我的 Java 学习笔记 · 第 11 篇:Condition.await 为什么必须放在 while 里

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 文档。

相关推荐
SimonKing2 小时前
一只离线鼠鼠,干翻了一堆在线格式转换网站
java·后端·程序员
程序员-Benothing2 小时前
Linux文本排序统计命令:sort、uniq、wc、cut、tr实战
java·开发语言·算法
clz13145212 小时前
双重检查锁 DCL 终极优化:懒加载 + 线程安全 + 高性能
java
CRMEB2 小时前
前后端技术栈全面换代!CRMEB 多商户(Java)v3.0更新预告
java·开发语言
开发者联盟league2 小时前
c++11 使用chrono库转换时间点和字符串,计算时间点间隔
java·c++·算法
我最爱吃鱼香茄子3 小时前
【毕业设计优选】人力资源管理系统|Java|SpringBoot|Vue|前后端分离|带详细文档+部署教学视频
java·vue·毕业设计·springboot
步行cgn3 小时前
Spring 只读事务面试详解
java·spring·面试
APIshop3 小时前
淘宝详情接口全解析:从官方开放平台到第三方数据服务
java·python·api
省钱兄--zs3 小时前
24小时自助健身房系统软件开发实战:从架构设计到部署全指南
java·数据仓库·spring boot·系统架构·需求分析