找回密码
 立即注册
首页 业界区 业界 Condition底层机制剖析:多线程等待与通知机制 ...

Condition底层机制剖析:多线程等待与通知机制

缄戈 4 小时前
概述

Condition 是一个多线程协调通信的工具类,可以让某些线程一起等待某个条件(condition),只有满足条件时,线程才会被唤醒。

  • 在使用Lock之前,使用的最多的同步方式应该是synchronized关键字来实现同步方式了。配合Object的wait()、notify()系列方法可以实现等待/通知模式。
  • Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式,但是这两者在使用方式以及功能特性上还是有差别的。
Object和Condition接口的一些对比。
对比项Object 监视器方法Condition前置条件获取对象的监视器锁调用  Lock.lock() 获取锁调用 Lock.newCondition() 获取 Condition 对象调用方法直接调用如:object.wait()直接调用如:condition.await()等待队列个数一个多个当前线程释放锁并进入等待队列支持支持当前线程释放锁并进入等待队列,在等待状态中不响应中断不支持支持当前线程释放锁并进入超时等待状态支持支持当前线程释放锁并进入等待状态到将来的某个时间不支持支持唤醒等待队列中的一个线程支持支持唤醒等待队列中的全部线程支持支持接口的介绍与示例

首先需要明白condition对象是依赖于lock对象的,意思就是说condition对象需要通过lock对象进行创建出来(调用Lock对象的newCondition()方法)。condition的使用方式非常的简单。但是需要注意在调用方法前获取锁。
  1. /**
  2. * condition使用示例:
  3. * 1、condition的使用必须要配合锁使用,调用方法时必须要获取锁
  4. * 2、condition的创建依赖于Lock lock.newCondition();
  5. */
  6. public class ConditionUseCase {
  7.     /**
  8.      * 创建锁
  9.      */
  10.     public Lock readLock = new ReentrantLock();
  11.     /**
  12.      * 创建条件
  13.      */
  14.     public Condition condition = readLock.newCondition();
  15.     public static void main(String[] args) {
  16.         ConditionUseCase useCase = new ConditionUseCase();
  17.         ExecutorService executorService = Executors.newFixedThreadPool(2);
  18.         executorService.execute(() -> {
  19.             //获取锁进行等待
  20.             useCase.conditionWait();
  21.         });
  22.         executorService.execute(() -> {
  23.             //获取锁进行唤起读锁
  24.             useCase.conditionSignal();
  25.         });
  26.     }
  27.     /**
  28.      * 等待线程
  29.      */
  30.     public void conditionWait() {
  31.         readLock.lock();
  32.         try {
  33.             System.out.println(Thread.currentThread().getName() + "拿到锁了");
  34.             System.out.println(Thread.currentThread().getName() + "等待信号");
  35.             condition.await();
  36.             System.out.println(Thread.currentThread().getName() + "拿到信号");
  37.         } catch (Exception e) {
  38.         } finally {
  39.             readLock.unlock();
  40.         }
  41.     }
  42.     /**
  43.      * 唤起线程
  44.      */
  45.     public void conditionSignal() {
  46.         readLock.lock();
  47.         try {
  48.             //睡眠5s 线程1启动
  49.             Thread.sleep(5000);
  50.             System.out.println(Thread.currentThread().getName() + "拿到锁了");
  51.             condition.signal();
  52.             System.out.println(Thread.currentThread().getName() + "发出信号");
  53.         } catch (Exception e) {
  54.         } finally {
  55.             //释放锁
  56.             readLock.unlock();
  57.         }
  58.     }
  59. }
  60. //执行结果
  61. 1 pool-1-thread-1拿到锁了
  62. 2 pool-1-thread-1等待信号 ---释放锁-线程等待 t1
  63. 3 pool-1-thread-2拿到锁了
  64. 4 pool-1-thread-2发出信号 --- 唤起线程t2释放锁
  65. 5 pool-1-thread-1拿到信号---t1继续执行
复制代码
如示例所示,一般都会将Condition对象作为成员变量。当调用await()方法后,当前线程会释放锁并在此等待,而其他线程调用Condition对象的signal()方法,通知当前线程后,当前线程才从await()方法返回,并且在返回前已经获取了锁。
接口常用方法

condition可以通俗的理解为条件队列。当一个线程在调用了await方法以后,直到线程等待的某个条件为真的时候才会被唤醒。这种方式为线程提供了更加简单的等待/通知模式。Condition必须要配合锁一起使用,因为对共享状态变量的访问发生在多线程环境下。一个Condition的实例必须与一个Lock绑定,因此Condition一般都是作为Lock的内部实现。
<ol>await() :造成当前线程在接到信号或被中断之前一直处于等待状态。
boolean await(long time, TimeUnit unit) :造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态---》是否超时,超时异常
awaitNanos(long nanosTimeout) :造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。返回值表示剩余时间,如果在nanosTimesout之前唤醒,那么返回值 = nanosTimeout - 消耗时间,如果返回值  0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))        LockSupport.unpark(node.thread);    return true;}[/code]这段代码主要做了两件事情:

  • 将头节点的状态更改为 CONDITION;
  • 调用 enq 方法,将该节点尾插入到同步队列中,关于 enq 方法请看 AQS 的底层实现这篇文章。
节点从等待队列移动到同步队列的过程如下图所示:
1.gif

被唤醒后的线程,将从 await() 方法中的 while 循环中退出(因为此时 isOnSyncQueue(Node) 方法返回 true),进而调用 acquireQueued() 方法加入到获取同步状态的竞争中。
成功获取了锁之后,被唤醒的线程将从先前调用的 await() 方法返回,此时,该线程已经成功获取了锁。
signalAll()
sigllAll 与 sigal 方法的区别体现在 doSignalAll 方法上,前面我们已经知道 doSignal 方法只会对等待队列的头节点进行操作, signalAll() 方法相当于对等待队列的每个节点均执行一次 signal() 方法,效果就是将等待队列中的所有节点移动到同步队列中。doSignalAll 的源码如下:
  1. public class ConditionObject implements Condition, java.io.Serializable
复制代码
该方法会将等待队列中的每一个节点都移入到同步队列中,即“通知”当前调用 condition.await() 方法的每一个线程。
await 与 signal/signalAll

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

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


  • 调用await方法后,将当前线程加入Condition等待队列中。当前线程释放锁。否则别的线程就无法拿到锁而发生死锁。自旋(while)挂起,不断检测节点是否在同步队列中了,如果是则尝试获取锁,否则挂起。
  • 当线程被signal方法唤醒,被唤醒的线程将从await()方法中的while循环中退出来,然后调用acquireQueued()方法竞争同步状态。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册