zoukankan      html  css  js  c++  java
  • Condition用例、源码分析详解(下)

    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);
    }

    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);
    }

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

    final boolean transferForSignal(Node node) {
      if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))// 更新节点的状态为 0,如果更新失败,只有一种可能就是节点被 CANCELLED了
        return false;
      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); // 唤醒节点上的线程.如果 node 的 prev 节点已经是signal 状态,那么被阻塞的 ThreadA 的唤醒工作由 AQS队列来完成
      return true;
    }

    图解分析
    执行完 doSignal 以后,会把 condition 队列中的节点转移到 aqs 队列上,逻辑结构图如下这个时候会判断 ThreadA 的 prev 节点也就是 head 节点的 waitStatus,如果大于 0 或者设置 SIGNAL 失败,表示
    节点被设置成了 CANCELLED 状态。这个时候会唤醒ThreadA 这个线程。否则就基于 AQS 队列的机制来唤醒,也就是等到 ThreadB 释放锁之后来唤醒 ThreadA
    被阻塞的线程唤醒后的逻辑: 前面在分析 await 方法时,线程会被阻塞。而通过 signal被唤醒之后又继续回到上次执行的逻辑中标注为红色部分的代码checkInterruptWhileWaiting 这个方法是干嘛呢?其实从名字就可以看出来,就是 ThreadA 在 condition 队列被阻塞的过程中,有没有被其他线程触发过中断请求

    public final void await() throws InterruptedException {
      if (Thread.interrupted()) throw new InterruptedException();
      Node node = addConditionWaiter();
      int savedState = fullyRelease(node);
      int interruptMode = 0;
      while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
          break;
      }
      if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
      interruptMode = REINTERRUPT;
      if (node.nextWaiter != null) // 如果被取消就清除
        unlinkCancelledWaiters();
      if (interruptMode != 0) reportInterruptAfterWait(interruptMode);

    }

     checkInterruptWhileWaiting

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

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

    final boolean transferAfterCancelledWait(Node node) {
      // 使用 cas 修改节点状态,如果还能修改成功,说明线程被中断时,signal 还没有被调用。这里有一个知识点,就是线程被唤醒,
      // 并不一定是在 java 层面执行了locksupport.unpark,也可能是调用了线程的
      // interrupt()方法,这个方法会更新一个中断标识,并且会唤醒处于阻塞状态下的线程。 
      if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
        enq(node); // 如果 cas 成功,则把node 添加到 AQS 队列
        return true;
      } // 如果 cas 失败,则判断当前 node 是否已经在 AQS 队列上,如果不在,则让给其他线程执行 当 node 被触发了 signal 方法时,node 就会被加到 aqs 队列上
      while (!isOnSyncQueue(node))// 循环检测 node 是否已经成功添加到 AQS 队列中。如果没有,则通过yield
      Thread.yield();
      return false;
    }

    acquireQueued
    这个方法在讲 aqs 的时候说过,是的当前被唤醒的节点ThreadA 去抢占同步锁。并且要恢复到原本的重入次数状态。调用完这个方法之后,AQS 队列的状态如下将 head 节点的 waitStatus 设置为-1,Signal 状态。
    reportInterruptAfterWait
    根据 checkInterruptWhileWaiting 方法返回的中断标识来进行中断上报。如果是 THROW_IE,则抛出中断异常如果是 REINTERRUPT,则重新响应中断
    private void reportInterruptAfterWait(int interruptMode) throws InterruptedException {
      if (interruptMode == THROW_IE) throw newInterruptedException();
       else if (interruptMode == REINTERRUPT)
      selfInterrupt();
    }
    至此,Condition源码讲解已接近尾声,接下来做一个最后的总结:
    Condition 总结
    await 和 signal 的总结,我把前面的整个分解的图再通过一张整体的结构图来表述,线程 awaitThread 先通过 lock.lock()方法获取锁成功后调用了 condition.await 方法进入等待队列,而另一个线程 signalThread 通过 lock.lock()方法获取锁成功后调用了 condition.signal 或者 signalAll 方法,使得线程awaitThread 能够有机会移入到同步队列中,当其他线程释放 lock 后使得线程 awaitThread 能够有机会获取lock,从而使得线程 awaitThread 能够从 await 方法中退出执行后续操作。如果 awaitThread 获取 lock 失败会直接进入到同步队列。
    阻塞:await()方法中,在线程释放锁资源之后,如果节点不在 AQS 等待队列,则阻塞当前线程,如果在等待队列,则自旋等待尝试获取锁。
    释放:signal()后,节点会从 condition 队列移动到 AQS等待队列,则进入正常锁的获取流程。
  • 相关阅读:
    VS2008 查找失效怎么办
    Winfrom弹出下拉编辑控件,DataGridView弹出查询对话框下拉录入,支持TextBox
    修改SQL Server 2005的默认端口
    C#操作XML小结
    金蝶KIS系列 KISBOS 二次开发学习资料 例子 习题 讲解
    Datawindow.net中实现让当前行选中,并且当前行以其他颜色显示
    sqlserver 截取字符串
    python3定时爬虫
    linux下安装pyenv及使用pyenv管理不同的python版本
    CentOS7下安装mysql最快捷方式及mysql远程访问连接实现
  • 原文地址:https://www.cnblogs.com/47Gamer/p/13061496.html
Copyright © 2011-2022 走看看