加锁失败
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
如果尝试加锁失败了,就会执行doAcquireShared方法,源码如下:
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
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);
}
}
这个方法和ReentrantLock的acquireQueued方法类似。addWaiter方法就是把当前线程封装成一个Node对象,然后添加到阻塞队列的末尾,然后返回这个Node对象,不同的是这里是以共享锁模式Node.SHARED进行等待的。
for循环首先获取前驱节点,如果前驱节点是head节点,表示当前节点有资格获取锁,所以就重新调用tryAcquireShared方法尝试获取锁。
获取锁失败的操作和ReentrantLock一样,找到一个可用的前驱节点并把它的waitStatus修改为Node.SIGNAL,然后当前线程通过LockSupport.park()方法进行阻塞。
获取锁成功后和ReentrantLock一样把当前节点设置成head节点,不同的是如果后续还有处于共享模式等待的节点,就会继续唤醒后续的节点,setHeadAndPropagate源码如下:
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
setHead(node);
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}
获取读锁成功后返回值是1,也就是propagate的值是1,所以会进入if语句内,如果下一个节点无法确定处于什么模式或者是共享模式,就尝试调用doReleaseShared方法唤醒后继节点。
private void doReleaseShared() {
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;
}
}
- 首先判断
head节点的waitStatus,如果是Node.SIGNAL表示可以唤醒后继节点,先通过CAS把waitStatus修改成0,防止多个线程同时唤醒一个节点,修改成功就调用unparkSuccessor方法进行唤醒。 - 因为被唤醒后的线程如果获取锁成功了就会把当前节点修改成
head节点,以便能后继续唤醒后继节点,所以这里判断h==head如果为true表示后继节点的线程没有获取锁成功,就不再继续唤醒;如果为false表示唤醒成功了修改了head节点,重新尝试唤醒下一个节点。 - 而如果
head节点的waitStatus为0表示它不能唤醒其他节点,就通过CAS把waitStatus修改成Node.PROPAGATE,这里的意思个人理解为如果head节点不能唤醒后续节点,那就向后传播也就是让第二个节点继续尝试唤醒后续节点。