概要
在JDK1.5之后出了Condition,它可以实现在同步语义中的等待/通知,以此来实现线程之间通信或协同。Condition和Object的wait和notify/notify在用法和效果上都十分的类似。
使用
例子
Condition经常可以用在生产者-消费者的场景中,我们看看官方的例子,这是一个经典的例子,看到会有熟悉的味道
1 | class BoundedBuffer { |
上面是一个往有界的buffer数组写/取数据的例子,为了防止写数组溢出或读不到数据,用了Condition来做控制。
我们可以看出Condition必须在临界区内使用,即必须先持有相应的锁,这跟synchronized和对象的监视器锁wait()/notify()很像。
常用方法
Condition的使用比较简单,总的来说只有等待和唤醒两套方法
1 | //当前线程进入等待状态直到被通知(signal)或中断。 |
1 | //唤醒一个等待在Condition队列上的线程 |
实现原理
Condition其实是AbstractQueuedSynchronizer
内部实现的ConditionObject
。Condition的实现跟AQS有很大的关系,所以如果不了解AQS的话,请先去了解一下AQS的实现。
Condition的原理大致是,当调用wait方法时,同步队列的头节点,即锁的持有者释放锁,并为当前线程创建一个节点加入到条件队列等待;当signal时,会释放条件队列的节点,并把这个节点接入到同步队列中等待获得锁。
初始化
Condition的初始化,我们可以看看ReentrantLock的,其最终是新建一个ConditionObject
对象,ConditionObject是AQS的一个内部类
1 | public Condition newCondition() { |
每调用一次newCondition则会创建一个Condition对象,所以一个AQS可以对应多个Condition,可以使用多个条件队列,从上面BoundedBuffer的例子可以看出来。
Condition内部是一个condition queue条件队列。注意这个队列的节点Node,就是跟AQS用的是同一个
1 | /** First node of condition queue. */ |
await
await方法有几个版本,我们看看最普通的那个
1 | public final void await() throws InterruptedException { |
从await方法已经可以大概了解Condition的处理流程,下面对里面的每个方法一一分析一下
第一步:加入条件队列
1 | //addConditionWaiter方法用来新建一个CONDITION的节点,然后加到条件队列尾部 |
第二步:释放锁
1 | final int fullyRelease(Node node) { |
1 | //如上所述,第一次肯定不在同步队列,会被刮起 |
接下来的acquireQueued
方法是AQS获取锁的方法,熟悉AQS的应该清楚,会一直阻塞直到排队被唤醒,竞争锁成功。
上述大致如图所示:
signal
唤醒同步队列有signal
和signalAll
方法,两者的区别是前者只唤醒同步队列的头节点,后者唤醒同步队列的所有节点,我们重点看看signal
唤醒操作通常由另一个线程来操作,就像生产者-消费者模式中,如果线程因为等待消费而挂起,那么当生产者生产了一个东西后,会调用 signal 唤醒正在等待的线程来消费。
1 | public final void signal() { |
signal过程大致如图所示:
总结
Condition为在临界区中提供了协同的操作,试想想如果我们需要做到类似挂起/唤醒的操作,那么比较好的做法是通过一个队列来完成,而由于AbstractQueuedSynchronizer自身实现的存在,所以条件队列的节点跟同步队列用的是同一个类。
Condition和Object的wait和notify/notify很像似,所以基本可以猜测实现原理也是差不多,只是前者在Java层面实现,后者在更底层的JVM实现。