我们先来看看 Condition 的使用场景,Condition 经常可以用在生产者-消费者的场景中,请看 Doug Lea 给出的这个例子:
1 | import java.util.concurrent.locks.Condition; |
ArrayBlockingQueue 采用这种方式实现了生产者-消费者,所以请只把这个例子当做学习例子,实际生产中可以直接使用 ArrayBlockingQueue。
构造Condition
首先,明确一点,Condition 是依赖于 ReentrantLock 的,不管是调用 await 进入等待还是 signal 唤醒,都必须获取到锁才能进行操作。
可以通过多次调用ReentrantLock的newCondition()方法获取多个ConditionObject对象。
1 | // ReentrantLock中 |
ConditionObject结构
我们首先来看下我们关注的 Condition 的实现类 AbstractQueuedSynchronizer 类中的 ConditionObject。
1 | public class ConditionObject implements Condition, java.io.Serializable { |
之前介绍过,在AQS中有一个保存等待获取锁的线程的阻塞队列,这里引入另一个叫条件队列的东西,该条件队列是一个单向链表,每一个Condition都会有自己的一个条件队列。示意图如下:
Condition执行流程
以生产者-消费者模式为例来说明,此处指的是最简单的流程,不考虑中断、signalAll、带超时参数等情况。
notFull条件:当生产者速度大于消费者速度时
- 生产者首先获取关联的锁,然后会判断任务队列是否满,如果满了会调用await()方法等待不满条件满足,此时会挂起当前生产者线程,并将其加入和notFull的条件队列,等待唤醒,然后释放锁,注意此处是完全释放锁,因为锁是可重入的。
- 唤醒操作通常由消费者线程来操作,唤醒前,也要先获取到锁,当消费者消费了一个任务时,会调用notFull的signal(),将和notFull的条件队列中的firstWaiter节点移到AQS的阻塞队列的队尾,等待获取锁,当获取锁成功await()方法返回,继续往下执行,将新生产的任务放入任务队列。
notEmpty条件:当消费者速度大于生产速度时
- 消费者首先获取关联的锁,然后会判断任务队列是否有任务,如果没有会调用notEmpty的await()方法等待该条件满足,此时会挂起当前线程,并将其加入notEmpty的条件队列,等待唤醒,然后释放锁,注意此处是完全释放锁,因为锁是可重入的。
- 此时唤醒该消费者由生产者来操作,前提也是要先获取关联的锁,当生产者生产一个任务放入任务队列后,此时会调用notEmpty的signal()方法,将notEmpty的条件队列的firstWaiter节点移到AQS的阻塞队列的队尾,然后该节点线程等待获取锁,当获取锁成功消费者的notEmpty的await()方法返回,继续往下执行,消费任务。