Condition.await 为什么必须放在 while 里
上一回看了 Java 线程的状态。这次继续看并发里一个容易写错的地方:线程拿到锁后发现条件还不满足,应该怎么等?
以银行转账为例:转出账户余额不足时,当前线程不能继续转账。但如果它拿着锁一直循环检查,其他线程也拿不到这把锁,余额就没有机会改变。Condition 让线程可以在等待条件时暂时放开关联的锁。
java
private final Lock bankLock = new ReentrantLock();
private final Condition sufficientFunds = bankLock.newCondition();
void transfer(int from, int to, int amount) throws InterruptedException {
bankLock.lock();
try {
while (accounts[from] < amount) {
sufficientFunds.await();
}
accounts[from] -= amount;
accounts[to] += amount;
sufficientFunds.signalAll();
} finally {
bankLock.unlock();
}
}
这里的 accounts 是银行账户余额数组。检查余额、修改余额和发出通知都由同一把锁保护。
await 做了什么
调用 await() 时,当前线程会进入这个 Condition 的等待队列,并原子地释放关联的锁。这样其他线程才有机会拿到锁、修改余额。收到通知后,等待线程还要重新竞争并拿回这把锁,await() 才会返回。
所以 signalAll() 不会让等待线程立刻执行。它只是唤醒等待者;发出通知的线程仍持有锁,直到 finally 中解锁后,等待者才能继续竞争。
为什么要用 while,不能只检查一次
通知表达的是"条件可能变了",不保证线程醒来时条件仍成立。比如多个转账线程都在等同一个账户,余额变化后它们被一起唤醒;先拿回锁的线程可能已经取走了余额,后面的线程必须再检查一次。Condition 也允许虚假唤醒。
因此应该反复检查条件:
java
while (条件不满足) {
condition.await();
}
若写成 if,线程醒来后只检查一次;条件仍不满足也会继续执行临界区里的操作。
通知也要配合状态变化
通常在共享状态发生变化、等待者可能因此继续时发出通知。教材的转账例子在余额更新后调用 signalAll(),让所有等待线程重新检查各自的条件。signal() 只唤醒一个等待线程;如果它醒来后仍不满足条件并再次等待,而其他等待者本来可以继续,系统就可能卡住。
调用 await()、signal() 或 signalAll() 时,必须持有这个 Condition 关联的锁。记住这几个步骤:拿锁、循环检查条件、不满足就 await;改变状态后发通知,最后释放锁。
参考:Java SE 21 Condition API 文档。