1、读写锁在同一时刻有多个读线程访问,在写线程时候所有的读锁和其他写线程都被阻塞。读写锁维护了一个读锁和一个写锁,通过分离读锁和写锁,并发性比其他排它锁都提升了。它具备3个特性:
使用一个整形编码维护读写状态,按位切割使用这个变量,其中高16位表示读,低16位表示写,如下图
1、获取状态:如果当前同步状态是S,写状态为 S & 0x0000FFFF(将高16位全部抹去),读状态为S >>> 16(无符号补0右移16位)
2、当写状态增加1是,等于S+1;读状态增加1是,等于S+(1<< 16)
3、当S不等于0时,当写状态(S & 0xooooFFFF)等于0时,则读状态(S>>>16)大于0,即读锁已被获取
写锁是支持重入的排它锁。如果当前线程已经获取了写锁,则增加写锁的状态。如果当前线程在获取读锁是,读锁已被获取或者该线程不是已经获取写锁的线程,则当前线程进入等待状态。
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
//获取锁的状态
int c = getState();
//获取写锁的状态
int w = exclusiveCount(c);
if (c != 0) {
// 存在读锁或者当前线程不是已获取读写锁的线程
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
setState(c + acquires);
return true;
}
if (writerShouldBlock() || !compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
获取写锁的公平和非公平如何保证
1、读锁是一个支持重进入的共享锁,能够被多个线程同时获取,在没有其他写线程访问时候,读锁总是可以被重新获取的。
2、当前线程重新获取读锁,则增加读的状态。
3、如果当前线程在获取读锁时候,其他线程获取了写锁,则进入等待状态
*/
Thread current = Thread.currentThread();
int c = getState();
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
int r = sharedCount(c);
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);
}
公平锁看是否有前驱节点
static final class FairSync extends Sync {
private static final long serialVersionUID = -2274990926593161451L;
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
}
非公平锁,直接返回false
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -8159625535654395037L;
final boolean writerShouldBlock() {
return false; // writers can always barge
}
final boolean readerShouldBlock() {
return apparentlyFirstQueuedIsExclusive();
}
}
锁的降级,是指把持住(当前拥有的)写锁,再获取到读锁,随后释放(先前拥有的)写锁的过程
//读写锁
private ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
//读锁
private ReadLock readLock=lock.readLock();
//写锁
private WriteLock writeLock=lock.writeLock();
private boolean update;
public void processData(){
//读锁获取
readLock.lock();
if(!update){
//必须先释放读锁
readLock.unlock();
//锁降级从获取写锁开始
writeLock.lock();
try {
if(!update){
//准备数据流程(略)
update=true;
}
//获取读锁。在写锁持有期间获取读锁
//此处获取读锁,是为了防止,当释放写锁后,又有一个线程T获取锁,对数据进行改变,而当前线程下面对改变的数据无法感知。
//如果获取了读锁,则线程T则被阻塞,直到当前线程释放了读锁,那个T线程才有可能获取写锁。
readLock.lock();
}finally{
//释放写锁
writeLock.unlock();
}
//锁降级完成
}
try {
//使用数据的流程
} finally{
//释放读锁
readLock.unlock();
}
}
锁降级的必要性:主要为了保证数据的可见性。如果当前线程不获取读锁而是直接释放写锁,假设此刻另一个线程(T)获取了写锁并修改了数据,那么当前线程无法感知线程T的数据更新。如果当前线程获取读锁,即遵循锁的降级步骤,则线程T将会被阻塞,直到线程使用数据并释放读锁之后,线程T才能获取读写锁更新数据。
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/u012998254/article/details/114171235
内容来源于网络,如有侵权,请联系作者删除!