Lock/Condition
是Java中提供的等待通知机制,使用Condition的await和signal
,类似于基于synchronized的wait和notify
,二者都可以实现等待通知机制。
但是,这两者在使用和实现方式上还是有差别的。比如:
- 等待通知机制涉及到同步队列和等待队列,Object的
wait/notify
只能拥有一个等待队列,而Condition可以拥有多个等待队列。 wait/notify
的等待队列管理是由JVM控制的,而Condition的是由jdk/juc实现的。
wait/notify
同步队列流程图:
Condition的同步队列流程图:
Lock Condition示例代码如下:
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
Thread t1 = new Thread(() -> {
lock.lock();
try {
condition.await();
System.out.println(Thread.currentThread().getName() + " await ok");
} catch (InterruptedException ignore) {
} finally {
lock.unlock();
}
}, "t1");
t1.start();
Thread t2 = new Thread(() -> {
lock.lock();
try {
// 将等待队列节点移动到AQS同步队列中(尾部)
condition.signal();
System.out.println(Thread.currentThread().getName() + " signal ok");
} finally {
// 唤醒AQS同步队列中阻塞的线程
lock.unlock();
}
}, "t2");
t2.start();
}
注意,await和signal必须在lock中,这是为了保证操作等待队列和同步队列的原子性,并且await有一个释放状态,也就是unlock的操作。
下面就以上述示例代码为例来分析下Condition的await和signal流程。对于线程t1来说,执行到await方法时,会添加一个节点到等待队列中,然后释放当前state,唤醒其他线程,最后阻塞自己,等待被唤醒。
public final void await() throws InterruptedException {
// 添加节点到等待队列,该方法未考虑场景,因此需在lock中
Node node = addConditionWaiter();
// 释放当前AQS state,会唤醒同步队列中等待的线程
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
// 不再同步队列中,当前线程阻塞,等待被唤醒
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 唤醒后重新获取state,获取成功后退出
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
当其他线程执行到signal时,会将之前在等待队列中的节点移动到同步队列中,最后执行unlock时唤醒同步队列中的线程。
public final void signal() {
// 当前占用state不是自己抛异常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// 开始唤醒第一个节点
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
// 唤醒第一个处于Node.CONDITION的节点,因为有些Node可能由于超时处于其他状态,就没有必要唤醒了
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
// 重置节点state,马上就要进入同步队列了
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
// 进入同步队列,如果前一个节点state>0,需要
Node p = enq(node);
int ws = p.waitStatus;
// ws>0表示前一个节点状态不对,需要设置为SIGNAL好唤醒新加入的节点node
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread); // 如果CAS设置失败,只能先唤醒了
return true;
}
Condition
接口方法如下所示,await多了一些超时等待机制,signalAll底层和signal是一样的,这里就不在展开。
至此,Lock Condition相关核心代码已分析完毕,由于Condition和AQS、Object.wait/signal
相关,因此关于AQS的资料可以参考AQS是如何控制线程的,关于Object.wait/signal
的可以参考浅谈synchronized与Object.wait/notify原理。
推荐阅读