AQS 源码解读系列--Condition 篇

39 篇文章 0 订阅
21 篇文章 0 订阅

上一篇我们解读了 ReentrantLock 的工作原理,ReentrantLock 作为 synchronized 关键字的替代,相对应的线程的通信 wait/notify/notifyAll 也有其替代,这就是 AQS 体系中的 Condition,本篇我们一起来了解一下 Condition 是如何工作的。

1. 源码解读

1.1. await()

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    // 将当前线程构造成 node 节点并加入 condition 等待队列
    Node node = addConditionWaiter();
    // 将当前线程持有的锁完全释放,因为会有重入的情况,
    // 因此这里是 fullyRelease,重入了多少次就释放多少次
    // 这里获取重入的次数,用于后续线程被唤醒并重新获得锁之后恢复锁的状态
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) {// 如果当前线程不在同步队列,继续挂起
    	// 将当前线程挂起
    	// 这里有一个疑问,线程被 park 之后,是在什么时候被 unpark 的呢?
    	// 当然是获得锁的线程释放锁之后,通过 unparkSuccessor() 方法唤醒的;
    	// 这里要结合 lock 的加锁解锁过程进行连贯。
        LockSupport.park(this);
        // 判断等待挂起的时候,线程有没有被中断过,获得中断状态,用于后面的中断处理
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    // 代码执行到这里,说明当前线程已经被唤醒,且节点已经在同步队列中
    // 被唤醒之后,node 的状态要结合 signal() 方法进行连贯
    // 自旋获取锁,并在获取锁之后响应中断
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        // 清除被取消的节点
        unlinkCancelledWaiters();
    if (interruptMode != 0)
    	// 响应中断
        reportInterruptAfterWait(interruptMode);
}

addConditionWaiter()

private Node addConditionWaiter() {
    Node t = lastWaiter;
    // If lastWaiter is cancelled, clean out.
    // 如果尾节点的 waitStatus 不为 condition 等待,说明已被取消,则清除队列中的被取消的节点
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    // 将当前线程构造成节点,加入 condition 等待队列
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}
// 这里将遍历链表并清除被取消的节点的过程贴出来,
// 主要是温习一下单向链表的遍历及删除节点的逻辑
private void unlinkCancelledWaiters() {
    Node t = firstWaiter;
    Node trail = null;
    while (t != null) {
        Node next = t.nextWaiter;
        if (t.waitStatus != Node.CONDITION) {
            t.nextWaiter = null;
            if (trail == null)
                firstWaiter = next;
            else
                trail.nextWaiter = next;
            if (next == null)
                lastWaiter = trail;
        }
        else
            trail = t;
        t = next;
    }
}

fullyRelease()

final int fullyRelease(Node node) {
    boolean failed = true;
    try {
        int savedState = getState();
        if (release(savedState)) {
            failed = false;
            return savedState;
        } else {
            throw new IllegalMonitorStateException();
        }
    } finally {
        if (failed)
            node.waitStatus = Node.CANCELLED;
    }
}

isOnSyncQueue()

// 判断当前线程是否在同步队列中
final boolean isOnSyncQueue(Node node) {
    // 如果当前节点的 waitStatus 仍旧为 CONDITION,
    // 或者前驱节点不存在,说明当前线程一定不在同步队列。
    // 由于同步队列中的头节点代表的是获得锁的线程,
    // 因此同步队列中被挂起的线程的节点一定存在前驱节点
    if (node.waitStatus == Node.CONDITION || node.prev == null)
        return false;
    // 如果当前节点存在后继节点,说明它一定在同步队列中
    // 这里需要注意区别 node 所构成的链表的特点:
    // node 既可以作为同步队列的节点,也可以作为等待队列的节点;
    // 当作为同步队列的节点时,使用两个指针分别指向前驱节点 prev 和后继节点 next;
    // 当作为等待队列的节点时,只有一个指向下一个节点的指针 nextWaiter
    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.
     */
     // 如果上面的条件都不满足,说明节点有可能在同步队列的尾部;
     // 或者刚刚由于多个线程的竞争,已经从同步队列尾部升到队列中间;
     // 所以下面的逻辑会去遍历同步队列查找当前节点
    return findNodeFromTail(node);
}

checkInterruptWhileWaiting()

private int checkInterruptWhileWaiting(Node node) {
    return Thread.interrupted() ?
    	// 如果线程被中断过,尝试将当前线程的 node 转移到同步队列
        (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
        0;
}
final boolean transferAfterCancelledWait(Node node) {
	// 如果可以 CAS 成功,说明 signal() 方法的调用先于线程的中断操作,
	// 这时将线程加入同步队列,并返回 true 来标识抛出异常
    if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
        enq(node);
        return true;
    }
    /*
     * If we lost out to a signal(), then we can't proceed
     * until it finishes its enq().  Cancelling during an
     * incomplete transfer is both rare and transient, so just
     * spin.
     */
    // 执行到这里说明线程的由于中断被唤醒的,此时线程还未加入同步队列,
    // 因此需要让出执行权,等待线程入队后再返回 false 标识线程执行 selfInterrupt() 方法
    while (!isOnSyncQueue(node))
        Thread.yield();
    return false;
}

acquireQueued()

// 已经进入同步队列的线程,自旋获取锁
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

1.2. signal()

public final void signal() {
	// 当前线程不是锁的 owner,抛出异常
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
    	// 唤醒等待队列的首节点
        doSignal(first);
}

doSignal()

// 该方法努力尝试在等待队列不为空的情况下一定要唤醒一个线程
private void doSignal(Node first) {
    do {
        // 将 firstWaiter 指针向后移动
        // 如果为空,说明等待队列已空
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        // 断开 first 节点的连接
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}

transferForSignal()

final boolean transferForSignal(Node node) {
    /*
     * If cannot change waitStatus, the node has been cancelled.
     */
    // 将 node 的状态置为初始状态
    if (!compareAndSetWaitStatus(node, 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).
     */
    // 自旋加入同步队列,入队成功后,方法返回的是当前节点的前驱节点,
    // 下面需要将这个前驱节点 p 的 waitStatus 状态设置为 SIGNAL,
    // 用来标识 p 释放锁之后唤醒后继节点 node
    Node p = enq(node);
    int ws = p.waitStatus;
    // ws > 0 说明前驱节点的状态是 CANCELLED,因此可以直接唤醒当前线程;
    // 将 waitstatus 更新成 SIGNAL,如果更新失败直接唤醒当前线程
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}

2. 图解

这里我们结合 lock 的加锁与解锁过程,将整个加锁解锁及线程通信串联起来。最终不难发现,这个过程的设计和 synchronized 惊人的相似。

  1. 只有一个线程 A 获取锁时,可直接获得锁,此时同步队列为空
    在这里插入图片描述
  2. 在 A 释放锁之前,线程 B 尝试获取锁,最终会获取失败并加入同步队列
    在这里插入图片描述
  3. 在 A 释放锁之前,线程 C 也来尝试获取锁,最终会获取失败也加入同步队列
    在这里插入图片描述
  4. 此时在线程 A 中执行了 condition.await() 方法,线程 A 会释放锁,并进入等待队列(在此之前等待队列一直为空),B 和 C 会竞争锁,假设 B 成功获取
    在这里插入图片描述
  5. 此时在线程 B 中也执行了 condition.await() 方法,线程 B 会释放锁,并进入等待队列,线程 C 成功获取锁
    在这里插入图片描述
  6. 此时在线程 C 中执行了 condition.signal() 方法,线程 A 会被移入同步队列,等待被 C 唤醒后重新获取锁
    在这里插入图片描述
  7. 此时线程 C 释放锁,线程 A 获得锁,假设 A 获得锁之后,执行了 condition.signal() 方法,线程 B 会被移入同步队列自旋
    在这里插入图片描述
  8. 此时线程 A 释放锁,线程 B 获得锁
    在这里插入图片描述
  9. 此时线程 B 释放锁
    在这里插入图片描述
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值