Java并发编程之ReentrantReadWriteLock(四)

58 阅读2分钟

释放读锁

读锁的释放其实主要还是对state进行操作,只不过因为共享锁和独占锁都使用state存储锁的数量,所以需要在释放读锁时修改state的高16位的值。

// ReentrantReadWriteLock
public void unlock() {
    sync.releaseShared(1);
}
// AQS
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}
// ReentrantReadWriteLock.Sync
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 != 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();
        int nextc = c - SHARED_UNIT;
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}

释放读锁最终调用的是tryReleaseShared方法,关于firstReaderfirstReaderHoldCount等属性的含义可以参考Java并发编程之ReentrantReadWriteLock番外篇 这里就不再过多赘述。

  • 首先判断当前线程是否是第一个获取读锁的线程,如果是的话判断持有锁数量是否是1,是的话表示释放锁之后当前线程就不再持有锁了,所以就把firstReader置为null,如果重入数量不是1,就对firstReaderHoldCount1表示减少一次重入。
  • 首先判断最后一个获取成功读锁的线程是否是当前线程,如果是的话就直接通过缓存的数据对锁数量减1,这样比线程本地获取锁数量更高效,如果当前线程不是最后一个线程那就只能到线程本地去获取锁数量,然后再减1。而如果当前线程持有锁数量为1,表示释放锁之后就不再持有锁,所以就从线程本地把记录持有锁数量的对象移除掉,防止出现内存泄漏。
  • 修改完线程本身持有锁的数量之后,还要修改总的读锁的数量,也就是修改state的高16位,一直重试到修改成功为止。

释放锁成功之后又调用了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;
    }
}

这里依然是判断头节点是否是Node.SIGNAL状态,是的话就来唤醒后继节点。因为head节点是在后继节点进行阻塞是把waitStatus修改为Node.SIGNAL的,目的就是为了能够唤醒后继节点,如果获取到的值是0的话可能是后继节点还没有执行到修改head节点的waitStatus,这里尝试修改从0修改为Node.PROPAGATE,如果失败了表示已经不是0了,就可能是SIGNAL了,所以继续重试尝试唤醒后继节点。