Java并发编程之ReentrantReadWriteLock(三)

91 阅读2分钟

加锁失败

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);
        }
    }

这个方法和ReentrantLockacquireQueued方法类似。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表示可以唤醒后继节点,先通过CASwaitStatus修改成0,防止多个线程同时唤醒一个节点,修改成功就调用unparkSuccessor方法进行唤醒。
  • 因为被唤醒后的线程如果获取锁成功了就会把当前节点修改成head节点,以便能后继续唤醒后继节点,所以这里判断h==head如果为true表示后继节点的线程没有获取锁成功,就不再继续唤醒;如果为false表示唤醒成功了修改了head节点,重新尝试唤醒下一个节点。
  • 而如果head节点的waitStatus0表示它不能唤醒其他节点,就通过CASwaitStatus修改成Node.PROPAGATE,这里的意思个人理解为如果head节点不能唤醒后续节点,那就向后传播也就是让第二个节点继续尝试唤醒后续节点。