Java ReentrantLock 源码阅读笔记(下)

186 阅读6分钟

Java ReentrantLock 源码阅读笔记(下)

20240917-20.jpg

在前一篇文章中我们分析了 ReentrantLocklockunlock 的实现,没有看过的同学可以点击这里.
今天我们继续来分析 ReentrantLockCondition 的实现。

Condition 实例创建

Condition 是一个接口:

public interface Condition {

    void await() throws InterruptedException;

    void awaitUninterruptibly();

    long awaitNanos(long nanosTimeout) throws InterruptedException;

    boolean await(long time, TimeUnit unit) throws InterruptedException;

    boolean awaitUntil(Date deadline) throws InterruptedException;

    void signal();

    void signalAll();
}

我们创建 Condition 实例是通过 ReentrantLock#newCondition() 方法实现的:

public Condition newCondition() {
    return sync.newCondition();
}

// ...
final ConditionObject newCondition() {
    return new ConditionObject();
}
// ...

Condition 的实现类是 ConditionObject,它是 AbstractQueuedSynchronizer 中的一个内部类,后面我们的代码分析也是基于 ConditionObject#await() 方法和 ConditionObject#notify() 方法的实现,当读懂了这两个方法后,其他的方法也都好理解了。

在开始关键的代码阅读之前我先大体的讲一下 CondtionObject 的工作原理,在前面的文章中我已经介绍过了,获取锁失败的线程会被添加到 AbstractQueuedSynchronizer 的等待队列中,等待获取锁的线程释放锁后,再依次把锁交给等待队列中的线程,直到等待队列中的线程都完成任务。
当获取到锁的线程可以通过 ConditionObject#await() 方法去释放锁(没有获取到锁调用会抛出异常),并把当前线程添加到 CondtionObject 的等待队列中,当其他获取到锁的线程调用 ConditionObject#notify() 后(同样没有获取到锁就调用会抛出异常)会将其中一个 CondtionObject 等待队列中的线程移动到 AbstractQueuedSynchronizer 的等待队列中去,等待别的线程都释放了锁后,就会继续恢复 ConditionObject#await() 方法的执行。

await() 方法实现

我们直接看 ConditionObject#await() 方法:

public final void await() throws InterruptedException {
    // 检查线程是否中断,如果中断直接抛出异常
    if (Thread.interrupted())
        throw new InterruptedException();
    // 在 Condtion 中添加一个等待的 node    
    Node node = addConditionWaiter();
    // 释放当前线程持有的锁,如果没有持有锁会抛出异常
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    // 死循环检查是否从 Condition 的等待队列中移动到 AQS 队列中,如果已经移动跳出循环
    while (!isOnSyncQueue(node)) {
        // 暂停当前线程
        LockSupport.park(this);
        // 检查线程是否中断,如果中断直接退出
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    // 这里表示已经被移动到 AQS 队列中了,通过 acquireQueued() 方法来等待其他的线程释放锁,这方法在上一篇文章中已经分析过了
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    // 移除等待过程中被删除的其他节点    
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    // 检查线程中断状态    
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

在理一下上面代码的逻辑:

  1. 检查当前线程是否中断,如果中断直接抛出异常(这里再多插一嘴,在 Java 中线程在运行中是无法被直接打断的,当需要中断线程的时候会给线程一个 interrupted 标识,然后在别的操作中就会检查这个标识,如果已经标识为中断,就会直接抛出 InterruptedException 异常去中断线程,Java 代码中有很多地方都会检查这个标识)。

  2. 通过 addConditionWaiter() 方法向 Condition 队列中添加一个等待节点。

  3. 通过 fullyRelease() 方法释放当前线程持有的锁,如果当前线程没有持有锁,会抛出异常。

  4. 通过 isOnSyncQueue() 方法死循环检查是否从 Condition 等待队列中已经移动到 AbstractQueuedSynchronizer 的等待队列中(signal() 方法会做这个操作,后面我们会看到),如果还在 Condition 的等待队列中就会调用 LockSupport.park() 方法暂停当前线程。

  5. 然后通过 acquireQueued() 方法等待 AbstractQueuedSynchronizer() 将锁给到当前线程,上一篇文章中已经分析过这个方法,本篇文章不再分析。

我们来看看 addConditionWaiter() 方法是如何向 Condition 队列中添加一个等待节点:

private Node addConditionWaiter() {
    // 如果当前线程没有获取到锁,抛出异常
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node t = lastWaiter;
    // If lastWaiter is cancelled, clean out.
    // 移除已经被删除的节点
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters();
        t = lastWaiter;
    }

    // 注意初始化的 Node 的 waitStatus 是 CONDITION,lock 中默认是 0。
    Node node = new Node(Node.CONDITION);

    // 将节点添加到 Condition 队列的尾部
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}

这里要注意和 lock() 的队列做一个区分,lock() 方法中的 NodewaitStatus0,而这里是 Node#CONDITIONlock() 方法中链表是双向链表用的变量是 Node#prevNode#next,而这里是单向链表用用的变量是 Node#nextWaiter

继续看看释放锁的方法 fullRelease()

final int fullyRelease(Node node) {
    try {
        int savedState = getState();
        if (release(savedState))
            return savedState;
        throw new IllegalMonitorStateException();
    } catch (Throwable t) {
        node.waitStatus = Node.CANCELLED;
        throw t;
    }
}

朴实无华的代码,就不多聊了,release() 方法在上一篇文章中已经分析过了。

再来看看 isOnSyncQueue() 方法是如何判断当前线程的节点已经被移动到了 AbstractQueuedSynchronizer 队列中:

final boolean isOnSyncQueue(Node node) {
    // 如果 waitStatus 是 CONDITION 或者他的前一个节点是空就表示在 Condition 的队列中。
    if (node.waitStatus == Node.CONDITION || node.prev == null)
        return false;
    // 如果 next 不为空表示已经在 AQS 的队列中    
    if (node.next != null) // If has successor, it must be on queue
        return true;
    /*
     * node.prev can be non-null, but not yet on queue because
     * the CAS to place it on queue can fail. So we have to
     * traverse from tail to make sure it actually made it.  It
     * will always be near the tail in calls to this method, and
     * unless the CAS failed (which is unlikely), it will be
     * there, so we hardly ever traverse much.
     */
    // 最后的判断方法是去 AQS 队列中一个一个找,是否有这个节点。 
    return findNodeFromTail(node);
}

signal() 方法实现

OK,我们趁热打铁,继续看看 signal() 方法是如何将 Condition 队列中的节点移动到 AbstractQueuedSynchronizer 中去的:

public final void signal() {
    // 同样先判断当前线程是否持有锁。
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    // 拿到 Condition 队列中的第一个节点,进入 doSignal() 方法.    
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}

继续跟踪 doSignal() 方法:

private void doSignal(Node first) {
    do {
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}

doSignal() 方法会通过 transferForSignal() 移动一个节点到 AbstractQueuedSynchronizer 队列中去,如果移动失败,就再移动下一个。

继续看 transferForSignal() 方法的实现:

final boolean transferForSignal(Node node) {
    /*
     * If cannot change waitStatus, the node has been cancelled.
     */
    // 将 waitStatus 通过 CAS 的方式从 CONDITION 修改成 0,修改失败直接返回.  
    if (!node.compareAndSetWaitStatus(Node.CONDITION, 0))
        return false;

    /*
     * Splice onto queue and try to set waitStatus of predecessor to
     * indicate that thread is (probably) waiting. If cancelled or
     * attempt to set waitStatus fails, wake up to resync (in which
     * case the waitStatus can be transiently and harmlessly wrong).
     */
    // 将节点添加到 AQS 中,返回的 p 是 node 的前一个节点. 
    Node p = enq(node);
    int ws = p.waitStatus;
    // 将前一个节点的 waitStatus 修改成 SIGNAL。
    if (ws > 0 || !p.compareAndSetWaitStatus(ws, Node.SIGNAL))
        // 恢复 await 方法对应的线程
        LockSupport.unpark(node.thread);
    return true;
}

继续看看 enq() 方法移动到 AbstractQueuedSynchronizer 队列的具体实现:

private Node enq(Node node) {
    for (;;) {
        Node oldTail = tail;
        if (oldTail != null) {
            node.setPrevRelaxed(oldTail);
            if (compareAndSetTail(oldTail, node)) {
                oldTail.next = node;
                return oldTail;
            }
        } else {
            initializeSyncQueue();
        }
    }
}

这段代码和 lock() 方法中的入队列的方式一样,朴实无华。

最后

其实 ReentrantLockCondition 中的 await() / signal()synchronizedObject 中的 wait() / notify() 逻辑几乎一样,只是实现的代码不一样而已,一个是在标准库中用 Java 的实现,一个是虚拟机中用 C/C++ 实现。希望通过这两篇文章能够让你能够更加深入理解 ReentrantLock,不再是人云亦云。