【JUC源码】JUC核心:AQS(三)同步队列源码分析(共享锁)

AQS 系列:

1.共享-加锁

共享锁和排他锁最大的不同在于:对于同一个共享资源,排他锁只能让一个线程获得,而共享锁还会去唤醒自己的后续节点,一起来获得该锁

acquireShared()

共享模式下,尝试获得锁

// 共享锁可以让多个线程获得,arg 可以被子类当做任意参数,比如当做可获得锁线程的最大个数
public final void acquireShared(int arg) {
    // tryAcquireShared 首先尝试获得锁,返回值小于 0 表示没有获得锁
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

下面是 ReentrantReadWriteLock 的 tryAcquireShared 方法。

注:这里不用 Reentrantlock 是因为 Reentrantlock 采用的是独占锁模式,没有 tryAcquireShared方法。

protected final int tryAcquireShared(int unused) {
    Thread current = Thread.currentThread(); 
    int c = getState(); 
    if (exclusiveCount(c) != 0 && 
        getExclusiveOwnerThread() != current)
        return -1;
    int r = sharedCount(c);
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        compareAndSetState(c, c + SHARED_UNIT)) {
        if (r == 0) {
            firstReader = current;
            firstReaderHoldCount = 1;
        } else if (firstReader == current) {
            firstReaderHoldCount++;
        } else {
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != getThreadId(current))
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
            rh.count++;
        }
        return 1;
    }
    return fullTryAcquireShared(current);
}

doAcquireShared()

管理同步队列(拿锁+休眠),大部分逻辑和独占锁 acquireQueued 一致的

private void doAcquireShared(int arg) {
    // node 追加到同步队列的队尾,acquireQueued 是排他模式
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            // 拿到 node 前一个节点
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                // 大于 0 说明成功获得锁
                if (r >= 0) {
                    // 此处和排它锁也不同,排它锁使用的是 setHead,这里的 setHeadAndPropagate 方法
                    // 不仅仅是把当前节点设置成 head,还会唤醒头节点的后续节点
                    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);
    // propagate > 0 表示已经有节点获得共享锁了
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        Node s = node.next;
        // 共享模式,还唤醒头节点的后置节点
        if (s == null || s.isShared())
            doReleaseShared();
    }
}

doReleaseShared(核心方法)

释放后置共享节点

private void doReleaseShared() {
    // 自旋,保证所有线程正常的线程都能被唤醒
    for (;;) {
        Node h = head;
        // 还没有到队尾,此时队列中至少有两个节点
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            // 如果头结点状态是 SIGNAL ,说明后续节点都需要唤醒
            if (ws == Node.SIGNAL) {
                // CAS 保证只有一个节点可以运行唤醒的操作
                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
        }
        // 退出自旋条件 h==head,一般出现于以下两种情况
        // 第一种情况,头节点没有发生移动,结束。
        // 第二种情况,因为此方法可以被两处调用,一次是获得锁的地方,一处是释放锁的地方,
        // 加上共享锁的特性就是可以多个线程获得锁,也可以释放锁,这就导致头节点可能会发生变化,
        // 如果头节点发生了变化,就继续循环,一直循环到头节点不变化时,结束循环。
        if (h == head)                   // loop if head changed
            break;
    }
}

2.共享-释放

releaseShared()

共享模式下,释放当前线程的共享锁

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        // 这个方法就是线程在获得锁时,唤醒后续节点时调用的方法
        doReleaseShared();
        return true;
    }
    return false;
}

注:排它锁和共享锁主要体现在加锁时,多个线程能否获得同一个锁。但在锁释放时,是没有排它锁和共享锁的概念和策略的,概念仅仅针对锁获取。

猜你喜欢

转载自blog.csdn.net/qq_33762302/article/details/114381225