Condition对象
Java提供了Condition对象来实现等待/通知。
Object对象提供了wait、waitAll、notify、notifyAll的方法用来实现线程的同步、等待和唤醒。Condition类提供了比wait/notify更丰富的功能,Condition对象由lock对象所创建的,同时一个Lock可以创建多个Condition对象,即创建多个对象监听器,这样就可以指定唤醒具体线程,而notify是随机唤醒线程。
Condition接口包含的方法
先看下Condition的源码
1 | public interface Condition { |
Condition主要提供了以下方法:
- await:使当前线程在接到信号或者中断之前一直阻塞等待;
- awaitUninterruptibly:使当前线程在收到信号之前一直等待。此处对中断不做响应
- awaitNanos:使当前线程在接到信号或者中断或者到达指定的纳秒时间之前一直阻塞等待
- await(long time, TimeUnit unit):使当前线程在接到信号或者中断或者到达指定的时间之前一直阻塞等待。此处可指定任意时间以及单位
- awaitUntil(Date deadline):使当前线程在接到信号或者中断或者到达截止日期之前一直阻塞等待
- signal:唤醒一个等待线程
- signalAll:唤醒所有的等待线程
AQS中的ConditionObject是实现Condition接口的实现。ConditionObject的等待队列是一个FIFO队列,队列的每个节点都是等待在Condition对象上的线程的引用,在调用Condition的await()方法之后,线程释放锁,构造成相应的节点进入等待队列等待。其中节点的定义复用AQS的Node定义。
ConditionObject
先看看这个对象的字段定义
1 | public class ConditionObject implements Condition, java.io.Serializable { |
ConditionObject是AQS的内部类,这样它就能访问到AQS的FIFO队列了。COnditionObject内部维护的是一个单向队列,它的首节点就是一个第一个被阻塞的线程节点。
下面我们挨个看下它的一些方法是怎么实现的。
await方法
1 | /** |
addConditionWaiter方法
1 | /** |
unlinkCancelledWaiters方法
1 | /** |
fullyRelease方法
1 | /** |
isOnSyncQueue
1 | /** |
整体流程
将当前线程封装为Node节点并加入到条件等待队列中;
释放锁,如果释放锁成功,则调用unparkSuccessor方法唤醒该节点在AQS的FIFO队列中的后继节点;
while死循环判断是否已经在AQS的FIFO队列中了(其他线程调用signal或者signalAll方法时,会触发这个节点被转移到FIFO队列的动作);
- 如果没有在FIFO队列中,则park当前线程,等待唤醒;
- 同时通过方法checkInterruptWhileWaiting判断线程是否在等待的过程中发生了中断,赋值interruptMode中断便签
关于中断模式: 1) 当在被通知前被中断则将中断模式设置为THROW_IE; 2) 当在被通知后则将中断模式设置为REINTERRUPT(因为acquireQueued不会响应中断)。
删除取消的后继等待节点。
根据中断模式抛出异常。
awaitUninterruptibly方法
1 | /** |
awaitNanos方法
1 | /** |
transferAfterCancelledWait方法
1 | /** |
awaitUntil方法
1 | /** |
await(long time, TimeUnit unit)方法
1 | /** |
signal方法
1 | /** 将等待时间最长的节点,也就是首节点移入AQS的FIFO队列去竞争锁 |
doSignal方法
1 | /** 将Condition队列中第一个非cancalled状态的节点从队列中移除,并移入AQS的FIFO队列中 |
transferForSignal方法
1 | /** |
signalAll方法
1 | /** |
doSignalAll方法
1 | /** |
总结
- 每一个创建的ConditionObject都维持这各自的一个单向的等待队列,但是同一个Lock的所有ConditionObject都共享一个AQS的FIFO同步队列;
- 当调用await方法时释放锁并进入阻塞状态,调用signal方法将条件等待队列中的首节点线程移动到AQS同步队列中并将其前继节点设置为SIGNAL或者直接唤醒线程使得被通知的线程能去获取锁;
- 调用await方法释放锁并将线程添加到条件等待队列中并没有采用死循环CAS设置(参考AQS.enq方法),因为Condition对象只能用于独占模式,而且在调用await之前会显示的获取独占锁,否则会抛出非法监视器状态异常。
- 调用signal方法将转移等待节点,也不需要CAS来保证,因为signal会确保调用者caller是获取独占锁的线程(通过isHeldExclusively方法来判断,如果为false会抛出非法监视器状态的异常)。