在使用Lock之前,我们使用的最多的同步方式应该是synchronized关键字来实现同步方式了。配合Object的wait()、notify()系列方法可以实现等待/通知模式。Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式,但是这两者在使用方式以及功能特性上还是有差别的。

image.png

1、Condition 的构建

首先我们需要明白condition对象是依赖于lock对象的,意思就是说condition对象需要通过lock对象进行创建出来(调用Lock对象的newCondition()方法)。consition的使用方式非常的简单。但是需要注意在调用方法前获取锁。condition可以通俗的理解为条件队列。当一个线程在调用了await方法以后,直到线程等待的某个条件为真的时候才会被唤醒。这种方式为线程提供了更加简单的等待/通知模式。Condition必须要配合锁一起使用,因为对共享状态变量的访问发生在多线程环境下。一个Condition的实例必须与一个Lock绑定,因此Condition一般都是作为Lock的内部实现。

  1. await() :造成当前线程在接到信号或被中断之前一直处于等待状态。
  2. await(long time, TimeUnit unit) :造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。
  3. awaitNanos(long nanosTimeout) :造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。返回值表示剩余时间,如果在nanosTimesout之前唤醒,那么返回值 = nanosTimeout - 消耗时间,如果返回值 <= 0 ,则可以认定它已经超时了。
  4. awaitUninterruptibly() :造成当前线程在接到信号之前一直处于等待状态。
  5. awaitUntil(Date deadline) :造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态。如果没有到指定时间就被通知,则返回true,否则表示到了指定时间,返回返回false。
  6. signal() 唤醒一个等待线程。该线程从等待方法返回前必须获得与Condition相关的锁。
  7. signalAll() 唤醒所有等待线程。能够从等待方法返回的线程必须获得与Condition相关的

2、Condition 使用范式

  1. public class ConditionUseCase {
  2. public Lock lock = new ReentrantLock();
  3. public Condition condition = lock.newCondition();
  4. // 等待通知,相当于Object#wait()
  5. public void conditionWait() {
  6. lock.lock();
  7. try {
  8. condition.await();
  9. }catch (Exception e){
  10. }finally {
  11. lock.unlock();
  12. }
  13. }
  14. // 发送通知,相当于Object#notify() 或Object#notifyAll();
  15. public void conditionSignal() {
  16. lock.lock();
  17. try {
  18. Thread.sleep(5000);
  19. condition.signal();
  20. }catch (Exception e){
  21. }finally {
  22. // 锁释放的时候才会唤醒线程,在signal()的时候并不会唤醒线程,这和Synchronized类似
  23. lock.unlock();
  24. }
  25. }
  26. }

3、Condition 原理浅析

3.1 等待队列

等待队列是一个FIFO的队列,在队列中的每个节点都包含了一个线程引用,该线程就是在Condition对象上等待的线程,如果一个线程调用了Condition.await()方法,那么该线程将会释放锁、构造成节点加入等待队列并进入等待状态。
一个Condition包含一个等待队列,Condition拥有首节点(firstWaiter)和尾节点(lastWaiter)。当前线程调用Condition.await()方法,将会以当前线程构造节点,并将节点从尾部加入等待队列,等待队列的基本结构如下图所示

image.png

在Object的监视器模型上,一个对象拥有一个同步队列和等待队列,而并发包中的Lock(更确切地说是同步器),我们可以通过方法名newCondition() 可以知道每次调用都会生成一个新的Condition对象,所以一个AQS 拥有一个同步队列和多个等待队列,其对应关系如下图所示

image.png

3.2 等待状态

调用Condition的await()方法(或者以await开头的方法),会使当前线程进入等待队列并释放锁,同时线程状态变为等待状态。当从await()方法返回时,当前线程一定获取了Condition相关联的锁,这个Synchronized的原理类似。如果从队列(同步队列和等待队列)的角度看await()方法,当调用await()方法时,相当于同步队列的首节点(获取了锁的节点)移动到Condition的等待队列中。
image.png
调用该方法的线程成功获取了锁的线程,也就是同步队列中的首节点,该方法会将当前线程构造成节点并加入等待队列中,然后释放同步状态,唤醒同步队列中的后继节点,然后当前线程会进入等待状态。

3.3 通知状态恢复

调用Condition的signal()方法,将会唤醒在等待队列中等待时间最长的节点(首节点),在唤醒节点之前,会将节点移到同步队列中。
image.png
所以调用__signal()方法并不会恢复线程,而是转移到同步队列中,和其他线程节点继续竞争锁,成功获取到锁之后,await()方法将返回即系执行。


_

4、参考文章

  1. Java多线程Condition接口原理详解
  2. Java并发之Condition
  3. 《Java并发编程的艺术》



_