摘要:condition用于显式的等待通知,等待过程可以挂起并释放锁,唤醒后重新拿到锁。
本文分享自华为云社区《AQS中的condition源码原理详细分析》,作者:breakDawn。
condition用于显式的等待通知,等待过程可以挂起并释放锁,唤醒后重新拿到锁。
和直接用lock\unlock去做等待通知的区别在于,lock是不会释放锁的,但是利用的condition的await则可以,且唤醒后会自动重新拿回锁。
- Lock lock = new ReentrantLock();
- Condition condition = lock.newCondition();
- public void conditionWait() throws InterruptedException {
- lock.lock();
- try {
- // if(xxxx)判断不满足条件,等待,释放锁
- condition.await();
- } finally {
- lock.unlock();
- }
- }
- public void conditionSignal() throws InterruptedException {
- lock.lock();
- try {
- // 做完事情了,通知condition上等待的开始抢占
- condition.signal();
- } finally {
- lock.unlock();
- }
- }
也提供了一些支持中断、支持超时的等待方法

Lock提供了newCondition接口给外部锁调用
而newCondition()返回的Condition是一个接口

这个接口的实现类是ConditionObject,放在AQS抽象类的内部类中

等待队列
等待过程
- public final void await() throws InterruptedException {
- if (Thread.interrupted())
- throw new InterruptedException();
- // 添加本线程到等待队列尾部
- Node node = addConditionWaiter();
- // 释放锁,唤醒同步队列中的后继节点
- int savedState = fullyRelease(node);
- int interruptMode = 0;
- // 如果已经在同步队列中了,说明被成功sign唤醒
- while (!isOnSyncQueue(node)) {
- // 阻塞挂起
- LockSupport.park(this);
- // 确认是否需要中断时就退出
- if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
- break;
- }
- // 在同步队列中,那就按同步队列的规则在队列中用CAS竞争同步状态
- if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
- interruptMode = REINTERRUPT;
- // 清理已经不是CONDITION状态的等待队列节点
- if (node.nextWaiter != null)
- unlinkCancelledWaiters();
- if (interruptMode != 0)
- reportInterruptAfterWait(interruptMode);
- }
唤醒过程signal()
1.检查调用signal时,是否当前线程获取了锁,不是则抛异常
- if (!isHeldExclusively())
- throw new IllegalMonitorStateException();
2.获取condition队列中的第一个等待节点
- Node first = firstWaiter;
- if (first != null)
- doSignal(first);
3.用CAS清除CONDITION状态
- if (!node.compareAndSetWaitStatus(Node.CONDITION, 0))
- return false;
4.调用AQS的enq(firstWaitNode),将这个节点放入到同步队列的队尾(需要CAS支撑?因为可能是共享的,即使获取了锁也需要竞争)
Node p = enq(node);
5.移动入同步队列成功后(可能经历了几次CAS),再用unpark方法唤醒,那个线程就进入了上面代码中Park之后的部分了
- int ws = p.waitStatus;
- if (ws > 0 || !p.compareAndSetWaitStatus(ws, Node.SIGNAL))
- LockSupport.unpark(node.thread);
6.如果是signalAll方法,则等待队列中每个节点都执行一次signal方法,全部移入同步队列中并唤醒(唤醒后他们很可能还会因为抢不到资源而阻塞,但队列位置不同了,也无法再通过sign唤醒了)
- do {
- Node next = first.nextWaiter;
- first.nextWaiter = null;
- transferForSignal(first);
- first = next;
- } while (first != null);