并发工具之Condition

参考: https://blog.csdn.net/javazejian/article/details/75043422 
1.Condition (AQS独占锁)
Condition 是一个多线程协调通信的工具类,可以让某些线程一起等待某个条件(condition),只有满足条件时,线程才会被唤醒.
condition 中两个最重要的方法,一个是 await,一个是 signal 方法。
await:把当前线程阻塞挂起;
signal:唤醒阻塞的线程;
调用 Condition,需要获得 Lock 锁,所以意味着会存在一个 AQS 同步队列。
查看源码类图关系可以知道,Condition接口的实现类是 ConditionObject,它是 AbstractQueuedSynchronizer 中的一个内部类

2.condition .await
调用Condition的await()方法(或者以await开头的方法),会使当前线程进入等待队列并释放锁,同时线程状态变为等待状态。
当从 await()方法返回时,当前线程一定获取了 Condition 相关联的锁。

public final void await() throws InterruptedException {
    if (Thread.interrupted()) //表示 await 允许被中断
        throw new InterruptedException();
// 1. 将当前线程包装成Node,尾插入到等待队列中
    Node node = addConditionWaiter(); //创建一个新的节点,节点状态为 condition,采用的数据结构仍然是链表
    // 2. 释放当前线程所占用的lock,在释放的过程中会唤醒同步队列中的下一个节点
int savedState = fullyRelease(node); //释放当前的锁,得到锁的状态,并唤醒 AQS 队列中的一个线程
    int interruptMode = 0; //如果当前节点没有在同步队列上,即还没有被 signal,则将当前线程阻塞
    while (!isOnSyncQueue(node)) { //判断这个节点是否在 AQS 队列上,第一次判断的是 false,因为前面已经释放锁了
        // 3. 当前线程进入到等待状态
	 LockSupport.park(this);//通过 park 挂起当前线程
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
// 当这个线程醒来,会尝试拿锁, 当 acquireQueued 返回 false 就是拿到锁了
// interruptMode != THROW_IE -> 表示这个线程没有成功将 node 入队,但 signal 执行了 enq 方法让其入队了
// 4. 自旋等待获取到同步状态(即获取到lock)
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT; // 将这个变量设置成 REINTERRUPT
// 如果 node 的下一个等待者不是 null, 则进行清理,清理 Condition 队列上的节点;否则,无需清理
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
// 如果线程被中断了,需要抛出异常;否则不需要处理
// 5. 处理被中断的情况
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

当前线程调用condition.await()方法后,会使得当前线程释放lock然后加入到等待队列中,直至被signal/signalAll后会使得当前线程从等待队列中移至到同步队列中去,直到获得了lock后才会从await方法返回,或者在等待时被中断会做中断处理。

3.addConditionWaiter
这个方法的主要作用是把当前线程封装成 Node,添加到等待队列.
这里的队列不再是双向链表,而是单向链表.

/**
 * Adds a new waiter to wait queue.
 * @return its new wait node
 */
private Node addConditionWaiter() {
    Node t = lastWaiter; 
    // If lastWaiter is cancelled, clean out.
// 如果 lastWaiter 不等于空并且waitStatus 不等于 CONDITION 时,把这个节点从链表中移除
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
// 构建一个Node,waitStatus=CONDITION
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}

4.fullyRelease
彻底的释放锁,什么叫彻底呢?就是如果当前锁存在多次重入,那么在这个方法中只需要释放一次,就会把所有的重入次数归零。

/**
 * Invokes release with current state value; returns saved state.
 * Cancels node and throws exception on failure.
 * @param node the condition node for this wait
 * @return previous sync state
 */
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;
    }
}

5.isOnSyncQueue
判断当前节点是否在同步队列中,返回 false 表示不在,返回 true 表示在.
如果不在 AQS 同步队列,说明当前节点没有唤醒去争抢同步锁,所以需要把当前线程阻塞起来,直到其他的线程调用 signal 唤醒;
如果在 AQS 同步队列,意味着它需要去竞争同步锁去获得执行程序执行权限;
为什么要做这个判断呢?原因是在 condition 队列中的节点会重新加入到 AQS 队列去竞争锁。也就是当调用 signal的时候,会把当前节点从 condition 队列转移到 AQS 队列;

/**
 * Returns true if a node, always one that was initially placed on
 * a condition queue, is now waiting to reacquire on sync queue.
 * @param node the node
 * @return true if is reacquiring
 */
final boolean isOnSyncQueue(Node node) {
    if (node.waitStatus == Node.CONDITION || node.prev == null)
        return false;
    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);
}

思考:如何判断某个线程 ThreadA 被封装的节点是否在AQS队列中?
(1).如果 ThreadA 的 waitStatus 的状态为 CONDITION,说明它存在于 condition 队列中,不在 AQS 队列。因为AQS 队列的状态一定不可能有 CONDITION ;
(2).如果 node.prev 为空,说明也不存在于 AQS 队列,原因是 prev=null 在 AQS 队列中只有一种可能性,就是它是head 节点,head 节点意味着它是获得锁的节点。
(3).如果 node.next 不等于空,说明一定存在于 AQS 队列中,因为只有 AQS 队列才会存在 next 和 prev 的关系;
(4).findNodeFromTail,表示从 tail 节点往前扫描 AQS 队列,一旦发现 AQS 队列的节点和当前节点相等,说明节点一定存在于 AQS 队列中

6.Condition.signal
await 方法会阻塞 ThreadA,然后 ThreadB 抢占到了锁获得了执行权限,这个时候在 ThreadB 中调用了 Condition 的 signal()方法,将会唤醒在等待队列中节点.

public final void signal() {
    if (!isHeldExclusively()) // 先判断当前线程是否获得了锁,这个判断比较简单,直接用获得锁的线程和当前线程相比即可
        throw new IllegalMonitorStateException();
    Node first = firstWaiter; // 拿到 Condition 队列上第一个节点
    if (first != null)
        doSignal(first);
}

7.Condition.doSignal
对 condition 队列中从首部开始的第一个 condition 状态的节点,执行 transferForSignal 操作,将 node 从 condition 队列中转换到 AQS 队列中,同时修改 AQS 队列中原先尾节点的状态.

private void doSignal(Node first) {
    do {
	 // 从 Condition 队列中删除 first 节点
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null; // 将 next 节点设置成 null
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}

8.AQS.transferForSignal
该方法先是 CAS 修改了节点状态,如果成功,就将这个节点放到 AQS 队列中,然后唤醒这个节点上的线程。
此时,那个节点就会在 await 方法中苏醒

/**
 * Transfers a node from a condition queue onto sync queue.
 * Returns true if successful.
 * @param node the node
 * @return true if successfully transferred (else the node was
 * cancelled before signal)
 */
final boolean transferForSignal(Node node) {
    /*
     * If cannot change waitStatus, the node has been cancelled.
     */
    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).
     */
    Node p = enq(node); // 调用 enq,把当前节点添加到 AQS 队列。并且返回返回按当前节点的上一个节点,也就是原 tail 节点
    int ws = p.waitStatus;
// 如果上一个节点的状态被取消了, 或者尝试设置上一个节点的状态为 SIGNAL 失败了(SIGNAL 表示: 他的 next 节点需要停止阻塞)
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread); // 唤醒节点上的线程
    return true; //如果 node 的 prev 节点已经是 signal 状态,那么被阻塞的 ThreadA 的唤醒工作由 AQS 队列来完成
}

9.被阻塞的线程唤醒后的逻辑
前面在分析 await 方法时,线程会被阻塞。而通过 signal 被唤醒之后又继续回到上次执行的逻辑中如下部分的代码

if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
    break;

checkInterruptWhileWaiting:
如果当前线程被中断,则调用 transferAfterCancelledWait 方法判断后续的处理应该是抛出 InterruptedException 还是重新中断。

private int checkInterruptWhileWaiting(Node node) {
    return Thread.interrupted() ?
        (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
        0;
}

这里需要注意的地方是,如果第一次 CAS 失败了,则不能判断当前线程是先进行了中断还是先进行了 signal 方法的调用,可能是先执行了 signal 然后中断,也可能是先执行了中断,后执行了 signal,当然,这两个操作肯定是发生在 CAS 之前。
这时需要做的就是等待当前线程的 node 被添加到 AQS 队列后,也就是 enq 方法返回后,返回 false 告诉 checkInterruptWhileWaiting 方法返回 REINTERRUPT(1),后续进行重新中断。
简单来说,该方法的返回值代表当前线程是否在 park 的时候被中断唤醒:
(1).如果为 true 表示中断在 signal 调用之前,signal 还未执行,那么这个时候会根据 await 的语义,在 await 时遇到中断需要抛出 interruptedException,返回 true 就是告诉 checkInterruptWhileWaiting 返回 THROW_IE(-1)。
(2).如果返回 false,否则表示 signal 已经执行过了,只需要重新响应中断即可。

10.transferAfterCancelledWait
使用 cas 修改节点状态,如果还能修改成功,说明线程被中断时,signal 还没有被调用。
这里有一个知识点,就是线程被唤醒,并不一定是在 java 层面执行了 locksupport.unpark,也可能是调用了线程的 interrupt()方法,这个方法会更新一个中断标识,并且会唤醒处于阻塞状态下的线程。

/**
 * Transfers node, if necessary, to sync queue after a cancelled wait.
 * Returns true if thread was cancelled before being signalled.
 *
 * @param node the node
 * @return true if cancelled before the node was signalled
 */
final boolean transferAfterCancelledWait(Node node) {
    if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
        enq(node); //如果 cas 成功,则把 node 添加到 AQS 队列
        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.
     */
    while (!isOnSyncQueue(node)) // 循环检测 node 是否已经成功添加到 AQS 队列中
        Thread.yield(); // 如果没有,则通过 yield
    return false;
}

11.reportInterruptAfterWait
根据 checkInterruptWhileWaiting 方法返回的中断标识来进行中断上报:
如果是 THROW_IE,则抛出中断异常;
如果是 REINTERRUPT,则重新响应中断;

/**
 * Throws InterruptedException, reinterrupts current thread, or
 * does nothing, depending on mode.
 */
private void reportInterruptAfterWait(int interruptMode)
    throws InterruptedException {
    if (interruptMode == THROW_IE)
        throw new InterruptedException();
    else if (interruptMode == REINTERRUPT)
        selfInterrupt();
}

12.Condition 总结
参考: https://www.jianshu.com/p/28387056eeb4
condition 是要和 lock 配合使用的也就是 Condition 和 Lock 是绑定在一起的,而 Lock 的实现原理又依赖于AQS;
锁机制的实现上,AQS内部维护了一个同步队列,如果是独占式锁的话,所有获取锁失败的线程的尾插入到同步队列;
同样的,condition内部也是使用同样的方式,内部维护了一个 等待队列,所调用condition.await方法的线程会加入到等待队列中,并且线程状态转换为等待状态。

(1).await方法被调用:同步队列的头节点尾插入到等待队列;

(2).signal方法被调用:等待队列的头节点尾插入到同步队列;

(3).通过使用condition提供的await和signal/signalAll方法就可以实现 “等待/通知机制”而这种机制能够解决最经典的问题就是“生产者与消费者问题。
await和signal和signalAll方法就像一个开关控制着线程A(等待方)和线程B(通知方)。它们之间的关系可以用下面一个图来表现得更加贴切:

线程awaitThread先通过lock.lock()方法获取锁成功后调用了condition.await方法进入等待队列,
而另一个线程signalThread通过lock.lock()方法获取锁成功后调用了condition.signal或者signalAll方法,
使得线程awaitThread能够有机会移入到同步队列中,当其他线程释放lock后使得线程awaitThread能够有机会获取lock,
从而使得线程awaitThread能够从await方法中退出,执行后续操作。如果awaitThread获取lock失败会直接进入到同步队列。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值