ReentrantReadWriteLock,可重入的读写锁,一个ReentrantReadWriteLock对象中,包含一个Sync同步器对象、一个ReadLock读锁对象 以及一个WriteLock写锁对象。
Sync、ReadLock、WriteLock都是ReentrantReadWriteLock的内部类,其中Sync继承自AbstractQueuedSynchronizer,所以ReentrantReadWriteLock也是基于AQS框架的实现。
ReentrantReadWriteLock支持公平和非公平模式,其中读锁为共享锁,写锁为互斥锁,他们共用一个AQS同步器。
AQS中只有一个记录状态的整形变量state,ReentrantReadWriteLock中,将其拆分为高16位和低16位来使用,
高16位表示读锁的持有计数,实际持有量 = state >>> 16,即无符号右移16位;
低16位表示写锁的持有计数,实际持有量 = state & (1 << 16) - 1,即1左移16位再减1之后(高位八个0,低位八个1),与state按位与。
ReentrantReadWriteLock构造函数,及提供读锁、写锁的方法如下:
public ReentrantReadWriteLock() {
this(false);
}
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }
接下来以非公平模式为例,分析读写锁的加锁、解锁流程。
读锁(共享锁)的获取
ReadLock#lock()
public void lock() {
sync.acquireShared(1);
}
/*
实际调用的是AQS的acquireShared方法,其中tryAcquireShared由AQS子类实现,doAcquireShared由AQS自身实现。
先尝试获取锁,如果失败则进入队列,之后再尝试获取。
*/
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
tryAcquireShared
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);
//如果当前线程不需要阻塞,并且读锁的持有计数小于最大值,并且CAS修改state成功,则表示当前线程已经获得了读锁
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
if (r == 0) {
/*
如果读锁的持有计数为0,则把当前线程设为第一个获取读锁的线程(严格来讲,firstReader是最后一个将共享计数从0改为1的唯一
线程,并且一直没有完全释放掉锁)
*/
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
//如果之前读锁已经被持有,并且第一个获取读锁的线程就是当前线程,只改变其持有计数
firstReaderHoldCount++;
} else {
/*
如果之前读锁已经被持有,且firstReader不是当前线程,那么获取cachedHoldCounter(上一个获取到读锁的线程的持有计数)
*/
HoldCounter rh = cachedHoldCounter;
/*
如果rh == null,也就是说cachedHoldCounter还没有缓存(firstReader的计数用的是firstReaderHoldCount,而不是
cachedHoldCounter,所以当前线程可能是第二个获取到读锁的线程),
或者rh.tid != getThreadId(current),即上一个获取到读锁的线程并不是当前线程;
也就是说,如果缓存没有命中,则从当前线程的ThreadLocal变量中获取它的HoldCounter(没有则初始化一个)
*/
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
/*
如果缓存命中,但是在本次获取读锁之前,当前线程已经把读锁全部释放掉了(在tryReleaseShared释放锁的方法中,可能
调用readHolds.remove()方法,清除了线程本地ThreadLocal变量),这时需要将readHolds设置一遍。
*/
else if (rh.count == 0)
readHolds.set(rh);
//当前线程的读锁计数自增
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);
}
readerShouldBlock
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -8159625535654395037L;
final boolean writerShouldBlock() {
return false; // writers can always barge
}
final boolean readerShouldBlock() {
/* As a heuristic to avoid indefinite writer starvation,
* block if the thread that momentarily appears to be head
* of queue, if one exists, is a waiting writer. This is
* only a probabilistic effect since a new reader will not
* block if there is a waiting writer behind other enabled
* readers that have not yet drained from the queue.
*/
return apparentlyFirstQueuedIsExclusive();
}
}
//非公平模式下判断读线程是否需要阻塞,就是判断当前等待队列中头结点的后继结点是否在等待写锁。
final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
return (h = head) != null &&
(s = h.next) != null &&
!s.isShared() &&
s.thread != null;
}
fullTryAcquireShared
tryAcquireShared方法中描述的仅仅是当前读线程不需要阻塞且CAS拿锁一次成功的场景,而fullTryAcquireShared方法,是全版本的获取读锁方法,考虑了需要阻塞、重入、CAS失败等情况,是对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) {
//如果写锁被其他线程持有,返回-1,表示拿锁失败
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
/*
这里readerShouldBlock为true,但是没有直接返回-1,就是考虑到有重入的情况。
如果firstReader == current,肯定是重入,直接放过去。
*/
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
} else {
if (rh == null) {
rh = cachedHoldCounter;
/*
这里判断是否命中缓存,最终保证ch指向当前线程的读锁计数。
如果rh.count == 0,则表示当前线程并非重入,线程本地保存读锁计数也就没什么意义,调用remove移除,然后返回-1;
如果rh.count != 0,则表示当前线程为重入,这里不做处理,放过去。
*/
if (rh == null || rh.tid != 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");
//后面类似tryAcquireShared中获取读锁成功后的处理,不同的是,这里如果没有成功会进行自旋。
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 != getThreadId(current))
rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
cachedHoldCounter = rh; // cache for release
}
return 1;
}
}
}
doAcquireShared
如果 tryAcquireShared(包含fullTryAcquireShared)方法获取读锁失败,则进入doAcquireShared方法:
private void doAcquireShared(int arg) {
//添加等待结点,这里通过参数指定为共享模式
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
//整体和互斥模式用到的acquireQueued方法类似,区别在于拿锁用tryAcquireShared方法,以及拿到锁后调用的是setHeadAndPropagate方法。
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
setHeadAndPropagate
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
setHead(node);
/*
* Try to signal next queued node if:
* Propagation was indicated by caller,
* or was recorded (as h.waitStatus either before
* or after setHead) by a previous operation
* (note: this uses sign-check of waitStatus because
* PROPAGATE status may transition to SIGNAL.)
* and
* The next node is waiting in shared mode,
* or we don't know, because it appears null
*
* The conservatism in both of these checks may cause
* unnecessary wake-ups, but only when there are multiple
* racing acquires/releases, so most need signals now or soon
* anyway.
*/
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
/*
如果当前结点的后继也是共享结点(等待读锁),则调用doReleaseShared唤醒该后继结点。
这里后继结点可能为null,因为当前结点可能是尾结点,或者后继结点刚入队,当前结点的next指针还没指向它;但这些并不妨碍调用
doReleaseShared,正如官方注释中说的,这些保守的检查可能会导致不必要的唤醒。
*/
if (s == null || s.isShared())
doReleaseShared();
}
}
先对if判断前的注释做一个解读:
在当前结点获取共享锁成功后,尝试唤醒其后继结点,如果满足以下两个条件:
1、调用者指明了需要传播,也就是入参propagate > 0,表示还有剩余共享资源可以获得;或者被一个之前的操作(比如setHead之前或之后修改了h.waitStatus)记录了需要传播。注意:这里使用 waitStatus < 0,而非直接判断PROPAGATE状态,是因为PROPAGATE有可能会被修改为SIGNAL状态,这是无法精确感知的。
2、当前结点的后继结点在共享模式下等待(比如等待读锁),或者我们压根不知道后继结点,因为某些情况下next指针为null。
这两种检查的保守性可能会导致不必要的唤醒,但只有在有多个同步获取/释放时才会如此,所以大多数情况下现在或很快就需要信号。
这里引入了一个行为叫做传播,意思是当前获取共享锁的线程,同时需要唤醒其后继结点(前提是该后继结点也处于共享模式)。
这种行为不难理解,比如读锁,因为它是共享的,所以等待队列中有一个结点获得了读锁,意味着在资源充足的情况下,后面一连串等待读锁的结点都有资格去获取它。所以一个共享结点获得锁时,会尝试唤醒其后继结点,后继结点获得锁时,再唤醒自己的后继结点,依次传播下去,直到遇到一个排他的结点(等待写锁的线程)。
传播的条件(if条件):propagate > 0 || h == null || h.waitStatus < 0 || (h = head) == null || h.waitStatus < 0
propagate,它是tryAcquireShared方法的返回值。
大于0,表示本次获取共享锁成功,并且后续的获取也可能成功(剩余信号量充足);
等于0,表示本次获取共享锁成功,但是后续的获取不会成功(剩余信号量不足);
小于0,表示本次获取共享锁失败。
propagate > 0,表示剩余资源充足,共享锁的获取是可传播的,直接判断当前结点的后继结点是否为共享模式,然后调用doReleaseShared进行唤醒。
h == null 和 (h = head) == null,目前没有找到对应场景,因为此时队列的结构已经是完整的,不管是原head结点还是当前结点作为head,都不会为null,此处应该是防范未知情况做的兜底。
如果propagate == 0,表示后续资源不足,为什么还要判断两个 h.waitStatus < 0,去尝试唤醒后继结点呢?
因为在并发环境中,当前结点拿到锁之后,进行if判断之前或者过程中,可能有线程释放了共享锁,也就是说仅仅依靠propagate的值来判断是否还有共享资源,以及是否传播,是不准确的。所以我们需要尽可能的去感知共享资源的释放。
共享资源的释放需要用到 releaseShared方法:
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
tryReleaseShared,释放共享资源,这是具体的锁实现类需要重写方法,AQS框架这里不关注;
doReleaseShared,唤醒结点,这个是AQS的方法,只要某个线程释放共享锁成功,那么就会走到唤醒结点的流程。
doReleaseShared方法源码如下:
private void doReleaseShared() {
/*
* Ensure that a release propagates, even if there are other
* in-progress acquires/releases. This proceeds in the usual
* way of trying to unparkSuccessor of head if it needs
* signal. But if it does not, status is set to PROPAGATE to
* ensure that upon release, propagation continues.
* Additionally, we must loop in case a new node is added
* while we are doing this. Also, unlike other uses of
* unparkSuccessor, we need to know if CAS to reset status
* fails, if so rechecking.
*/
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
这个方法中,判断头结点的 waitStatus 如果为-1,则表示其后继结点正在阻塞,需要唤醒,这是唤醒结点的常规操作。
但如果 waitStatus 是0,则把它修改为 -3,也就是PROPAGATE状态。这个操作的意义,在于引入一个PROPAGATE状态,用来和 -1、0做出区别,表示当前有共享资源释放了,如果哪个结点拿到共享锁的时候读到了这个状态,记得唤醒一下共享的后继结点。这里其实就是为setHeadAndPropagate的 if 判断做铺垫。
下面结合实际并发场景分析 compareAndSetWaitStatus(h, 0, Node.PROPAGATE) 是如何跟 if条件里的 h.waitStatus < 0配合的。
以下面这个模型为例:
假设当前结点A拿到了共享锁,并且返回的propagate == 0,此时 if条件里的第一个 h.waitStatus < 0,也就是原head结点O的状态,可能有如下几种情况:
1、状态为0,比如:
- A刚进队列就拿锁成功,没有经过 shouldParkAfterFailedAcquire 方法中将O状态改为-1的过程,O为初始状态0;
- A已经将O状态改为-1,但是O拿锁成功后发现propagate > 0,调用doReleaseShared方法唤醒了A,并且把自己状态从-1改为0;
- A已经将O状态改为-1,但是在A执行setHead之前,其他线程释放共享锁,调用doReleaseShared方法将O状态从-1改为0
2、状态为-3,比如:
- O拿锁成功后发现propagate > 0,调用doReleaseShared方法把自己状态从0改为-3,然后A进队拿锁成功;
- 上面三种状态为0的场景,如果紧随其后出现了其他线程释放共享锁,都会调用doReleaseShared方法将O状态从0改为-3。
3、状态为-1,比如:
- A进队列后拿锁失败,在 shouldParkAfterFailedAcquire 方法中将O的状态改为-1,然后A自旋拿锁成功;而在改为-1前,O的状态有可能是初始的0,也有可能发生了doReleaseShared调用被改成了-3,对应了官方注释中的 “PROPAGATE status may transition to SIGNAL”。
综合以上的场景可以说明,当前结点在获取共享锁成功时,原头结点的状态可能是-3、-1、0中的任何一个,而且每种状态都可能经历了其他线程释放共享锁,调用doReleaseShared方法的过程。所以根据 h.waitStatus来判断是否唤醒后继结点,是无法精准匹配到共享锁释放的场景的。
疑问1:为什么这里使用h.waitStatus < 0,而不使用<=0 或判断其他值?
分析:首先,虽然-3、-1、0每种状态的演变过程,都可能包含了共享锁的释放,但是0表示无状态,-1表示阻塞唤醒,它们并不具备传播语义;其次,如果直接判断 h.waitStatus == -3的话,这个状态又是不稳定的,可能在shouldParkAfterFailedAcquire方法中被改为-1;所以只能再放宽范围,使用h.waitStatus < 0。
疑问2:既然这里 h.waitStatus < 0 的条件是宽泛且可能不成立的,那它存在的价值是什么,可不可以不要?
分析:尽管这个条件是宽泛且不够准确的,甚至分析完第二个h.waitStatus < 0之后再回来看,发现即使第一个h.waitStatus < 0条件去掉,第二个也完全能够兜底,但是这里第一个h.waitStatus < 0的意义在于尽可能地响应那些 “在当前结点获取到共享锁并且通过setHead修改头指针之前发生的共享资源释放”,并尝试唤醒其后继结点,这也是对头结点PROPAGATE状态的一种响应,这个条件是完全合理的。
如果第一个h.waitStatus < 0不成立(比如等于0),就会走到第二个h.waitStatus < 0的判断,新的h指示当前结点A。
这时A的状态同样有以下几种情况:
1、状态为0,比如:
- 后继结点B刚入队,还没有执行到shouldParkAfterFailedAcquire方法中将A状态改为-1的语句;
- 结点B将A的状态改为-1后,有其他线程释放了共享锁,调用doReleaseShared方法,将A的状态改为0;
2、状态为-3,比如:
- A的状态本来为0,但有其他线程释放了共享锁,调用doReleaseShared方法,将A的状态改为-3;
3、状态为-1,比如:
- 后继结点B拿锁失败后,在shouldParkAfterFailedAcquire方法中将A状态改为-1。而在改为-1前,A的状态有可能是初始的0,也有可能发生了doReleaseShared调用被改成了-3。
疑问1:同样的,为什么这里又是h.waitStatus < 0?如果这里再出现h.waitStatus == 0的情况,是否会影响到后继结点的唤醒?
分析:首先这里-3是可用的,但是-3这个状态不稳定,可能被改为-1,所以条件放宽到h.waitStatus < 0;
然后看0这个状态,如果此时
1)后继结点B还没执行到shouldParkAfterFailedAcquire方法中将A状态改为-1的语句,那么有可能B即将/正在执行 tryAcquireShared拿锁,或者执行完shouldParkAfterFailedAcquire方法后再次自旋拿锁,此时是不需要对B进行唤醒的。
2)有其他线程释放了共享锁,调用doReleaseShared方法,将A的状态从-1改为0,那么在doReleaseShared中就会对B结点进行唤醒,也不需要再通过A去唤醒。
所以总结就是第二个h.waitStatus < 0起到了兜底的作用,它可以保证后继结点B一定被唤醒(或者本来就醒着)。
疑问2:
第二个h.waitStatus < 0意义是什么,能否不要呢?
分析:该条件的意义在于尽可能地响应那些 “在当前结点获取到共享锁并且通过setHead修改头指针之后发生的共享资源释放”,并尝试唤醒其后继结点。
这个条件如果不要的话,又正好前面的if条件都不满足,那么这里就不会对后继结点进行判断和唤醒,结合此时头结点A的状态:
- 如果为0,还好,后继结点还没有休眠,会尝试自旋地获取共享锁;
- 如果为-1,问题也不大,虽然后继结点没有第一时间尝试唤醒,但是后续共享锁释放的时候会调用doReleaseShared,发现是-1,就会改成0,并且唤醒后继结点;
- 如果为-3,且后继结点处于休眠状态的话,就麻烦了。后继结点只能依赖doReleaseShared方法来唤醒,但是doReleaseShared中只会针对状态为-1的头结点,进行后继结点的唤醒,-3是不处理的,也就是说没有线程会再去唤醒后继结点了。
所以总体上来看,两个h.waitStatus < 0条件是配合doReleaseShared方法中设置的PROPAGATE状态来发挥作用的。这两个条件的覆盖面不同,但目的都是在 propagate == 0的情况下,也尽可能地尝试唤醒共享模式的后继结点,提高AQS的使用效率。
读锁(共享锁)的释放
ReadLock#unlock()
public void unlock() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
tryReleaseShared是AQS子类需要实现的方法,而doReleaseShared由AQS框架实现;
doReleaseShared方法之前已经分析过,主要用来为队列头结点设置传播状态,以及唤醒头结点的后继结点,这里主要看下tryReleaseShared方法。
tryReleaseShared
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
//先判断firstReader缓存,如果命中则更新缓存
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
} else {
HoldCounter rh = cachedHoldCounter;
//再判断cachedHoldCounter缓存,如果未命中,则从线程本地的readHolds中读取
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
int count = rh.count;
//持有数量小于等于1的话,再释放就没有了,ThreadLocal变量readHolds也就没有保留价值了,下次用的时候再initialValue就行。
if (count <= 1) {
readHolds.remove();
if (count <= 0)
throw unmatchedUnlockException();
}
//线程本地持有计数自减
--rh.count;
}
//CAS自旋保证修改state值成功,这里规定只有读锁全部被释放掉,没有任何线程持有时,才返回true。
for (;;) {
int c = getState();
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;
}
}
写锁(互斥锁)的获取
WriteLock#lock()
public void lock() {
sync.acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire
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)
/*
c != 0仅表示锁被占用,什么锁不知道。w == 0表示写锁没被占用,如果它成立则说明读锁被占用。
这里if条件比较绕,总的来说就是:
在有锁被占用的前提下,当且仅当写锁被当前线程占用时,该线程才有资格获取写锁(重入),其他情况一律不行。
*/
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;
}
//这里走到的是另一个大前提:任何锁都没有被占用
//非公平模式下,writerShouldBlock恒定返回false,所以直接CAS抢锁,这里如果失败了,就会回到acquire方法中进队列。
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
后面的 acquireQueued、addWaiter方法都是AQS的实现,在 ReentrantLock中分析过,此处不再关注。
写锁(互斥锁)的释放
WriteLock#unlock()
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
写锁的释放和获取一样,都是直接调用了AQS的模板实现。
tryRelease
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;
}
公平模式和非公平模式的差异
公平与非公平的区别主要在于执行拿锁操作前,是否需要判断队列中已经有线程在等待。
ReentrantReadWriteLock中,该差异体现在 readerShouldBlock 和 writerShouldBlock两个方法上,这两个方法分别在 tryAcquireShared 和 tryAcquire方法中被调用。
非公平模式下的实现:
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -8159625535654395037L;
final boolean writerShouldBlock() {
return false; // writers can always barge
}
final boolean readerShouldBlock() {
/* As a heuristic to avoid indefinite writer starvation,
* block if the thread that momentarily appears to be head
* of queue, if one exists, is a waiting writer. This is
* only a probabilistic effect since a new reader will not
* block if there is a waiting writer behind other enabled
* readers that have not yet drained from the queue.
*/
return apparentlyFirstQueuedIsExclusive();
}
}
公平模式下的实现:
static final class FairSync extends Sync {
private static final long serialVersionUID = -2274990926593161451L;
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
}
hasQueuedPredecessors是AQS的框架实现,ReentrantLock中也有分析过。
最后总结下读锁和写锁的互斥性:
1、写锁被持有的情况下,只有持有写锁的线程自己可以申请读锁(也叫作锁降级),其他线程不能申请读锁;
2、读锁被持有的情况下,持有读锁的线程必须保证获取读锁之前先持有了写锁,才能再次申请写锁(重入),单纯的只持有读锁是不能申请写锁的(不支持升级),而其他线程不能申请写锁。
锁降级的合理性:
在一个线程独占写锁进行写数据的时候,其他线程是无法读数据的,但是要保证写后的数据对线程自己可见,就得让自己申请到读锁,毕竟他读自己写的数据,读取时机是可控制的,也不会影响到其他线程,所以锁降级是合理的。反之,如果不给他读锁,那么写完数据之后,一旦写锁被别的线程抢到,那自己写完的数据连自己都读不到了,这就不太合理了。
锁升级的不合理性:
当写锁没被独占的时候,一个线程在读数据,由于读锁是共享的,同时可能有很多其他线程也在读,这个时候其中一个线程来申请写锁,想要修改数据,显然是不合理的,一旦让他修改,那么多在读的线程,让他们挂起吗?显然不行,让他们接着读?会读到不一致的结果,也不行。所以锁升级是不合理的。