shared模式下加锁和exclusive模式下加锁的机制大致相同,都是尝试加锁,如果失败那么入队阻塞的逻辑。
但是在获取到锁的逻辑有点区别:
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
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();
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} catch (RuntimeException ex) {
cancelAcquire(node);
throw ex;
}
}
重点是setHeadAndPropagate方法,在exclusive下只需要setHead就可以了,看下Propagate都做了些什么:
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) 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) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}
调用了doReleaseShared()方法,acquire方法最终调用了release方法,得看下为什么。原因其实也很简单,shared模式下是允许多个线程持有一把锁的,其中tryAcquire的返回值标志了是否允许其他线程继续进入。如果允许的话,需要唤醒队列中等待的线程。其中doReleaseShared方法的逻辑很简单,就是唤醒后继线程。这样就实现了Propagate的语义。
其中PROPAGATE = -3这个状态是个令人费解的状态,在早期的版本中是不存在这个状态的。这个状态的意思是即使tryAcquire返回值为0,那么也得去看下是不是需要唤醒后继结点。
因此acquire的主要逻辑就是尝试加锁,如果允许其他线程继续加锁,那么唤醒后继线程,如果失败,那么入队阻塞等待。