java进阶之路-java同步系列之ReentrantReadWriteLock源码解析
Posted LuckyZhouStar
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java进阶之路-java同步系列之ReentrantReadWriteLock源码解析相关的知识,希望对你有一定的参考价值。
本文参照java同步系列之ReentrantReadWriteLock源码解析
简介
读写锁是一种特殊的锁,它把对共享资源的访问分为读访问和写访问,多个线程可以同时对共享资源进行读访问,但是同一时间只能有一个线程对共享资源进行写访问,使用读写锁可以极大地提高并发量。
读锁源码解读
lock操作
@ReservedStackAccess
protected final int tryAcquireShared(int unused)
/*
* Walkthrough:
* 1. If write lock held by another thread, fail.
* 2. Otherwise, this thread is eligible for
* lock wrt state, so ask if it should block
* because of queue policy. If not, try
* to grant by CASing state and updating count.
* Note that step does not check for reentrant
* acquires, which is postponed to full version
* to avoid having to check hold count in
* the more typical non-reentrant case.
* 3. If step 2 fails either because thread
* apparently not eligible or CAS fails or count
* saturated, chain to version with full retry loop.
*/
Thread current = Thread.currentThread();
//获取当前的同步状态
int c = getState();
//如果当前有排它锁,或者当前的线程不是自己,就返回-1,阻塞当前线程,后面这一步操作是为了判断如果已经有写锁的话,是可以立即获取读锁的
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
//获取读锁的数量
int r = sharedCount(c);
//满足条件的话,尝试更新state状态
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 != LockSupport.getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
return 1;
return fullTryAcquireShared(current);
/**
* Full version of acquire for reads, that handles CAS misses
* and reentrant reads not dealt with in tryAcquireShared.
*/
final int fullTryAcquireShared(Thread current)
/*
* This code is in part redundant with that in
* tryAcquireShared but is simpler overall by not
* complicating tryAcquireShared with interactions between
* retries and lazily reading hold counts.
*/
HoldCounter rh = null;
for (;;)
int c = getState();
if (exclusiveCount(c) != 0)
if (getExclusiveOwnerThread() != current)
return -1;
// else we hold the exclusive lock; blocking here
// would cause deadlock.
else if (readerShouldBlock())
// Make sure we're not acquiring read lock reentrantly
if (firstReader == current)
// assert firstReaderHoldCount > 0;
else
if (rh == null)
rh = cachedHoldCounter;
if (rh == null ||
rh.tid != LockSupport.getThreadId(current))
rh = readHolds.get();
if (rh.count == 0)
readHolds.remove();
if (rh.count == 0)
return -1;
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
if (compareAndSetState(c, c + SHARED_UNIT))
if (sharedCount(c) == 0)
firstReader = current;
firstReaderHoldCount = 1;
else if (firstReader == current)
firstReaderHoldCount++;
else
if (rh == null)
rh = cachedHoldCounter;
if (rh == null ||
rh.tid != LockSupport.getThreadId(current))
rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
cachedHoldCounter = rh; // cache for release
return 1;
我们来看看大致的逻辑:
(1)先尝试获取读锁;
(2)如果成功了直接结束;
(3)如果失败了,进入doAcquireShared()方法;
(4)doAcquireShared()方法中首先会生成一个新节点并进入AQS队列中;
(5)如果头节点正好是当前节点的上一个节点,再次尝试获取锁;
(6)如果成功了,则设置头节点为新节点,并传播;
(7)传播即唤醒下一个读节点(如果下一个节点是读节点的话);
(8)如果头节点不是当前节点的上一个节点或者(5)失败,则阻塞当前线程等待被唤醒;
(9)唤醒之后继续走(5)的逻辑;
在整个逻辑中是在哪里连续唤醒读节点的呢?
答案是在doAcquireShared()方法中,在这里一个节点A获取了读锁后,会唤醒下一个读节点B,这时候B也会获取读锁,然后B继续唤醒C,依次往复,也就是说这里的节点是一个唤醒一个这样的形式,而不是一个节点获取了读锁后一次性唤醒后面所有的读节点。采用链表的形式进行唤醒操作。
unlock操作
protected final boolean tryReleaseShared(int unused)
Thread current = Thread.currentThread();
if (firstReader == current)
// assert firstReaderHoldCount > 0;
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount–;
else
HoldCounter rh = cachedHoldCounter;
if (rh == null ||
rh.tid != LockSupport.getThreadId(current))
rh = readHolds.get();
int count = rh.count;
if (count <= 1)
readHolds.remove();
if (count <= 0)
throw unmatchedUnlockException();
–rh.count;
for (;?
int c = getState();
//cas原理来释放当前的锁操作计数器
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
// Releasing the read lock has no effect on readers,
// but it may allow waiting writers to proceed if
// both read and write locks are now free.
return nextc == 0;
解锁的大致流程如下:
(1)将当前线程重入的次数减1;
(2)将共享锁总共被获取的次数减1;
(3)如果共享锁获取的次数减为0了,说明共享锁完全释放了,那就唤醒下一个节点;
ABC三个节点各获取了一次共享锁,三者释放的顺序分别为ACB,那么最后B释放共享锁的时候tryReleaseShared()才会返回true,进而才会唤醒下一个节点D。
写锁操作
lock操作
protected final boolean tryAcquire(int acquires)
/*
* Walkthrough:
* 1. If read count nonzero or write count nonzero
* and owner is a different thread, fail.
* 2. If count would saturate, fail. (This can only
* happen if count is already nonzero.)
* 3. Otherwise, this thread is eligible for lock if
* it is either a reentrant acquire or
* queue policy allows it. If so, update state
* and set owner.
*/
Thread current = Thread.currentThread();
int c = getState();
//排他锁数量
int w = exclusiveCount(c);
if (c != 0)
// (Note: if c != 0 and w == 0 then shared count != 0)
//如果有共享锁或者当前不是本线程直接返回false
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)如果有其它线程占有着写锁,尝试获取写锁失败;
(4)如果是当前线程占有着写锁,尝试获取写锁成功,state值加1;
(5)如果没有线程占有着锁(state==0),当前线程尝试更新state的值,成功了表示尝试获取锁成功,否则失败;
(6)尝试获取锁失败以后,进入队列排队,等待被唤醒;
(7)后续逻辑跟ReentrantLock是一致;
unlock操作
protected final boolean tryRelease(int releases)
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
int nextc = getState() - releases;
boolean free = exclusiveCount(nextc) == 0;
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
return free;
写锁释放的过程大致为:
(1)先尝试释放锁,即状态变量state的值减1;
(2)如果减为0了,说明完全释放了锁;
(3)完全释放了锁才唤醒下一个等待的节点;
总结
1)ReentrantReadWriteLock采用读写锁的思想,能提高并发的吞吐量;
(2)读锁使用的是共享锁,多个读锁可以一起获取锁,互相不会影响,即读读不互斥;
(3)读写、写读和写写是会互斥的,前者占有着锁,后者需要进入AQS队列中排队;
(4)多个连续的读线程是一个接着一个被唤醒的,而不是一次性唤醒所有读线程;
(5)只有多个读锁都完全释放了才会唤醒下一个写线程;
(6)只有写锁完全释放了才会唤醒下一个等待者,这个等待者有可能是读线程,也可能是写线程;
(7) 内部把state字段进行了拆分,高16位是共享锁的数量,低16位是排他锁的数量
以上是关于java进阶之路-java同步系列之ReentrantReadWriteLock源码解析的主要内容,如果未能解决你的问题,请参考以下文章
JAVA进阶之路-ReentrantLock的公平锁和非公平锁