详解Condition的await和signal等待/通知机制

Object的wait、notify与Condition的区别?
从整体上看,
Object的wait、notify是与对象监视器配合完成线程间的等待/通知机制,是java底层级别的;
而Condition是与Lock配合完成等待通知机制,是语言级别的,具有更高的可控制性和扩展性。
(1)Condition能够支持不响应中断,而Object提供的wait方法不支持;
(2)Condition支持多个等待队列,而Object只能支持一个;
(3)Condition支持设置超时时间,而Object不支持。


创建一个Condition对象是通过lock.newCondition()创建的,而这个方法实际上会new出一个ConditionObject对象,该类是AQS的一个内部类。同样的,Condition内部也是使用了与AQS一样的方式,内部维护了一个等待队列,所有调用condition.await方法的线程会加入到等待队列中,并且线程状态转换为等待状态。
与AQS不同的是,等待队列是一个单向队列!!!等待队列通过头尾指针来管理等待队列。如下图所示:
在这里插入图片描述
我们可以多次调用lock.newCondition()方法创建多个condition对象,也就是一个lock可以持有多个等待队列。而在之前利用Object的方式实际上是指在**对象Object对象监视器上只能拥有一个同步队列和一个等待队列,而并发包中的Lock拥有一个同步队列和多个等待队列。**示意图如下:
在这里插入图片描述


await实现原理
当调用condition.await()方法后会使得当前获取lock的线程进入到等待队列,如果该线程能够从await()方法返回的话一定是该线程获取了与condition相关联的lock。

public final void await() throws InterruptedException {
  if (Thread.interrupted())
    throw new InterruptedException();
  // 1.将当前线程包装为Node,尾插到等待队列中
  Node node = addConditionWaiter();
  // 2.释放当前线程所占用的lock,释放后会唤醒同步队列中的下一个节点
  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) // clean up if cancelled
    unlinkCancelledWaiters();
  // 处理中断状况
  if (interruptMode != 0)
    reportInterruptAfterWait(interruptMode);
}

通过上述代码我们可以看到,await方法的执行如下:
在这里插入图片描述
在这里插入图片描述
**调用condition.await方法的线程必须是已经获得了lock,也就是当前线程是同步队列中的头结点。调用该方法后会使得当前线程所封装的Node尾插入到等待队列中。**如上图。


signal/signalAll实现原理
调用condition的signal或signalAll方法可以将等待队列中等待时间最长的结点移动到同步队列中,使得该结点能够有机会获得lock。
因为等待队列是先进先出的,所以等待队列的头结点必然会是等待时间最长的结点,也就是每次调用condition的signal方法是将头结点移动到同步队列中。
signal方法

public final void signal() {
  // 当前线程是否已经获取lock
  if (!isHeldExclusively())
    throw new IllegalMonitorStateException();
  // 获取等待队列的第一个节点,之后的操作都是针对这个节点
  Node first = firstWaiter;
  if (first != null)
    doSignal(first);
}

signal方法获取等待队列的第一个结点,然后调用dosignal方法。

private void doSignal(Node first) {
  do {
    if ( (firstWaiter = first.nextWaiter) == null)
      lastWaiter = null;
    // 将头结点从等待队列中移除
    first.nextWaiter = null;
    // transferForSignal方法对头结点做真正的处理
 } while (!transferForSignal(first) && (first = firstWaiter) != null);
}

dosignal方法将头结点先从等待队列中移除,接着调用transferForsignal方法真正对头结点做处理。

final boolean transferForSignal(Node node) {
  /*
  * If cannot change waitStatus, the node has been cancelled.
  */
  // 首先将节点状态更新为0
  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).
  */
  // 将节点使用enq方法尾插到同步队列中
  Node p = enq(node);
  int ws = p.waitStatus;
  if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
    LockSupport.unpark(node.thread);
  return true;
}

transferForsignal方法先将头结点的状态由Condition改为0,然后调用enq方法,将该结点尾插入到同步队列中。
通过上述代码分析,我们可以得出结论:调用condition的signal的前提条件是当前线程已经获取了lock,该方法会使得等待队列中的头节点即等待时间最长的那个节点移入到同步队列,而移入到同步队列后才有机会使得等待线程被唤醒,即从await方法中的LockSupport.park(this)方法中返回,从而才有机会使得调用await方法的线程成功退出。
signal执行示意图如下:

在这里插入图片描述
signalAll方法
signalAll方法与signal方法的区别体现在doSignalAll方法上,doSignal方法只会对等待队列的头结点进行操作,而doSignalAll的源码为:

private void doSignalAll(Node first) {
  lastWaiter = firstWaiter = null;
  do {
    Node next = first.nextWaiter;
    first.nextWaiter = null;
    transferForSignal(first);
    first = next;
 } while (first != null);
}

该方法将等待队列中的每一个结点都移到同步队列中,即,“通知”当前调用condition.await()方法的每一个线程。


上述内容仅为自己在学习过程中的理解,如有不足之处,请指正。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值