楼主: HannahChen0128
15 0

【016】wait () 和 Condition 的区别 [推广有奖]

  • 0关注
  • 0粉丝

等待验证会员

学前班

80%

还不是VIP/贵宾

-

威望
0
论坛币
0 个
通用积分
0
学术水平
0 点
热心指数
0 点
信用等级
0 点
经验
30 点
帖子
2
精华
0
在线时间
0 小时
注册时间
2018-12-6
最后登录
2018-12-6

楼主
HannahChen0128 发表于 2025-12-5 20:17:24 |AI写论文

+2 论坛币
k人 参与回答

经管之家送您一份

应届毕业生专属福利!

求职就业群
赵安豆老师微信:zhaoandou666

经管之家联合CDA

送您一个全额奖学金名额~ !

感谢您参与论坛问题回答

经管之家送您两个论坛币!

+2 论坛币

用 wait () 做点餐系统被骂后,Condition 教我给线程 “精准喊号”!

零、引入

你刚用 wait() 实现了外卖催单功能,信心爆棚,主动接下公司食堂的“智能点餐系统”开发任务,信誓旦旦地表示“线程协作稳如老狗”。可系统上线第一天就翻车:堂食顾客点的“番茄炒蛋”等了20分钟还没上桌,反倒是外卖订单接连不断被优先处理。顾客怒拍桌子,直呼“系统歧视堂食”,领导更是把你叫进办公室,拍桌声堪比 JVM 的 GC 回收:“3小时内修好,否则这个月全勤奖泡汤!”

你急得满头大汗,排查代码才发现问题所在:使用 wait() 和 notifyAll() 实现的订单唤醒机制,无论是堂食还是外卖订单,一旦唤醒,所有线程都挤到厨师面前,厨师随机抓取一个处理,毫无秩序可言。就在你准备把“离职申请.pdf”重命名为“全勤奖申诉.pdf”时,王哥叼着煎饼果子走过来,边嚼边说:“慌啥?wait() 这种‘广播喇叭’根本搞不定多条件等待!该用 Condition 给线程‘分群喊号’,比你这‘瞎唤醒’强十倍!”

一、先吐槽 wait ():多条件场景下就是 “添乱神器”

王哥抹了抹嘴角的葱花,指着你的代码笑出皱纹:“你把堂食和外卖订单放在同一个队列里,共用一把锁,还用同一个 wait(),就像餐厅只有一个广播喇叭——不管是谁的餐好了,全都喊‘有人取餐’。结果堂食客人跑过去发现是外卖单,外卖小哥赶来一看又是堂食,这不是乱套了吗?”

2.1 先搞懂 Condition 的核心优势(对比 wait ())

wait() 的问题是“一刀切”:它只能在单一条件下等待,且 notifyAll() 会唤醒所有等待线程,导致不必要的竞争与判断。而 Condition 提供了更细粒度的控制能力——一个锁可以绑定多个 Condition 实例,每个 Condition 可以独立 await() 和 signal(),实现“按组通信”。

换句话说,Condition 就像给不同类型的订单配备了专属对讲机:堂食订单用 A 频道,外卖订单用 B 频道。厨师想做堂食时,只呼叫 A 频道;外卖来了,才触发 B 频道通知,彻底避免无效唤醒。

2.2 示例代码

public class GoodCanteenDemo {
    // 使用 ReentrantLock 替代 synchronized
    static final ReentrantLock lock = new ReentrantLock();
    // 为堂食订单创建独立 Condition
    static final Condition dineInCond = lock.newCondition();
    // 为外卖订单创建独立 Condition
    static final Condition takeoutCond = lock.newCondition();

    // 堂食订单队列
    static Queue<String> dineInOrders = new LinkedList<>();
    // 外卖订单队列
    static Queue<String> takeoutOrders = new LinkedList<>();

    public static void main(String[] args) {
        // 厨师线程:负责处理订单
        new Thread(() -> {
            while (true) {
                lock.lock();
                try {
                    // 优先处理外卖订单(假设业务如此)
                    while (takeoutOrders.isEmpty()) {
                        if (!dineInOrders.isEmpty()) break;
                        System.out.println("厨师:暂无订单,歇会儿...");
                        takeoutCond.await(); // 等待外卖订单
                    }

                    // 先做外卖
                    if (!takeoutOrders.isEmpty()) {
                        String order = takeoutOrders.poll();
                        System.out.println("厨师:做外卖订单——" + order);
                    }
                    // 再看堂食
                    else if (!dineInOrders.isEmpty()) {
                        String order = dineInOrders.poll();
                        System.out.println("厨师:做堂食订单——" + order);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }, "厨师").start();

        // 堂食下单线程(顾客1)
        new Thread(() -> {
            lock.lock();
            try {
                dineInOrders.add("番茄炒蛋+米饭");
                System.out.println("顾客1:点了堂食——番茄炒蛋+米饭");
                dineInCond.signal(); // 只通知堂食相关的等待者
            } finally {
                lock.unlock();
            }
        }, "堂食顾客1").start();

        // 外卖下单线程(外卖员1)
        new Thread(() -> {
            lock.lock();
            try {
                takeoutOrders.add("宫保鸡丁+饮料");
                System.out.println("外卖员1:提交了外卖订单——宫保鸡丁+饮料");
                takeoutCond.signal(); // 只唤醒外卖处理逻辑
            } finally {
                lock.unlock();
            }
        }, "外卖员1").start();
    }
}

三、wait () vs Condition:核心区别大对比

对比维度 wait()/notify() Condition
底层锁机制 依赖 synchronized 配合 Lock 使用
等待队列数量 每对象仅一个等待队列 一个 Lock 可创建多个 Condition,对应多个等待队列
唤醒精度 notify()/notifyAll() 全体唤醒,易造成“虚假唤醒” signal()/signalAll() 可针对特定 Condition 唤醒,精准调度
灵活性 低,无法自定义等待策略 高,支持中断等待、超时等待、公平锁集成等

四、使用场景:什么时候用 wait (),什么时候用 Condition?

4.1 用 wait () 的场景:简单单条件等待

当系统中只有一个等待条件,且并发量不高时,使用 wait() 完全够用。例如:

  • 生产者-消费者模型中的基础版本(只有一个缓冲区)
  • 简单的资源池等待(如数据库连接池初始化完成前阻塞)

这类场景无需复杂控制,synchronized + wait/notify 已足够清晰简洁。

4.2 用 Condition 的场景:多条件分组等待

当存在多种等待状态或需要分类通知时,必须使用 Condition。典型例子包括:

  • 订单系统中区分堂食、外卖、预约订单的处理
  • 任务调度器中按优先级划分 High/Medium/Low 队列
  • 读写锁内部实现中 Read 和 Write 的独立等待机制

此时若强行使用 wait(),将导致大量线程被唤醒后又因条件不符重新进入等待,浪费 CPU 资源。

五、总结:3 句口诀 + 避坑指南

  1. 单条件用 wait,多条件用 Condition —— 条件越多,越要分通道通信。
  2. 广播唤醒是隐患,精准喊号才高效 —— notifyAll() 是“广而告之”,容易引发惊群效应。
  3. Lock 配 Condition,灵活掌控线程流 —— 更高级的同步控制离不开组合拳。

5.1 避坑指南(王哥的血泪史)

  • 别忘了 unlock():使用 Lock 必须确保 finally 中释放锁,否则死锁风险极高。
  • signal 要及时:订单加入后必须立即调用对应 Condition.signal(),否则线程可能永久挂起。
  • while 判断不能改成 if:await() 返回后仍需重新检查条件,防止虚假唤醒或信号丢失。
  • 不要混用 synchronized 与 Condition:它们属于不同体系,混合使用会导致行为不可预测。

六、最后说句实在的

早期 Java 并发编程依赖 wait()/notify() 是无奈之举,但如今有了更强大的工具,就不该再拿“老古董”硬扛复杂场景。就像王哥最后撂下的一句话:“你以为你在写代码?其实你是在设计流程。流程不清,代码再漂亮也是垃圾。”

从“广播喇叭”升级到“分群对讲机”,不只是技术选型的变化,更是对并发逻辑理解的跃迁。下次遇到多条件等待,别再拍脑袋用 wait() 了,试试 Condition,让线程真正“听懂指令”。

import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class GoodCanteenDemo {
    // 可重入锁:替代 synchronized,功能更强大
    static final Lock LOCK = new ReentrantLock();

    // 堂食专属 Condition(对讲机1):用于堂食订单的等待与唤醒
    static final Condition DINE_IN_COND = LOCK.newCondition();

    // 外卖专属 Condition(对讲机2):用于外卖订单的等待与唤醒
    static final Condition TAKEOUT_COND = LOCK.newCondition();

    // 堂食订单队列
    static Queue<String> dineInOrders = new LinkedList<>();

    // 外卖订单队列
    static Queue<String> takeoutOrders = new LinkedList<>();

    public static void main(String[] args) {
        // 厨师线程:现在能精准处理不同类型的订单
        new Thread(() -> {
            while (true) {
                LOCK.lock();
                try {
                    // 优先检查堂食订单
                    while (dineInOrders.isEmpty()) {
                        System.out.println("厨师:暂无堂食订单,等堂食顾客下单...");
                        DINE_IN_COND.await(); // 仅等待堂食通知
                    }

                    // 处理当前堂食订单
                    String dineInOrder = dineInOrders.poll();
                    System.out.println("厨师:做堂食订单——" + dineInOrder);

                    // 若堂食处理完还有空闲,再检查外卖订单
                    while (takeoutOrders.isEmpty()) {
                        System.out.println("厨师:暂无外卖订单,等外卖订单来...");
                        TAKEOUT_COND.await(); // 仅等待外卖通知
                    }

                    // 处理当前外卖订单
                    String takeoutOrder = takeoutOrders.poll();
                    System.out.println("厨师:做外卖订单——" + takeoutOrder);

                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    LOCK.unlock();
                }
            }
        }, "厨师").start();

        // 顾客1:点堂食
        new Thread(() -> {
            LOCK.lock();
            try {
                dineInOrders.add("番茄炒蛋+米饭");
                System.out.println("顾客1:点了堂食——番茄炒蛋+米饭");
                DINE_IN_COND.signal(); // 只通知堂食相关的线程
            } finally {
                LOCK.unlock();
            }
        }, "顾客1").start();

        // 外卖员1:提交外卖订单
        new Thread(() -> {
            LOCK.lock();
            try {
                takeoutOrders.add("黄焖鸡米饭+可乐");
                System.out.println("外卖员1:点了外卖——黄焖鸡米饭+可乐");
                TAKEOUT_COND.signal(); // 只唤醒外卖处理线程
            } finally {
                LOCK.unlock();
            }
        }, "外卖员1").start();
    }
}
跑起来可能出现的日志示例: 厨师:暂无堂食订单,等堂食顾客下单... 顾客1:点了堂食——番茄炒蛋+米饭 厨师:做堂食订单——番茄炒蛋+米饭 厨师:暂无外卖订单,等外卖订单来... 外卖员1:点了外卖——黄焖鸡米饭+可乐 厨师:做外卖订单——黄焖鸡米饭+可乐 “你看,”王哥说道,“原本使用 wait() 和 notifyAll() 的时候,所有线程都挤在一个等待队列里,一旦唤醒就是全员出动,但真正符合条件的可能只有一个。这就像在公司群里喊‘谁有空来帮忙’,结果设计、运维、司机全来了,只有一个人能干活,其余都是无效响应。” 他喝了口茶继续解释:“而 Condition 就像是给每个任务组配了专用对讲机。堂食用一个频道,外卖用另一个。你喊哪个频道,就只有对应的人听到并响应,避免资源浪费和混乱。” Condition 的核心优势(对比 wait/notify):
  • 多条件分组:一个 Lock 可以创建多个 Condition 实例,每个实例维护独立的等待队列。例如,餐厅中可以将堂食和外卖分别管理,互不干扰。
  • 精准唤醒:通过 signal() 或 signalAll() 只唤醒指定 Condition 上等待的线程,不会波及其它无关线程,提升效率与可控性。
  • 更灵活的控制机制:支持带超时的等待(awaitNanos, awaitUntil)、可中断等待(awaitUninterruptibly 除外),还能查询当前等待队列长度(getWaitQueueLength()),比传统的 wait() 更加丰富和安全。
冷知识小插曲: 有人调侃说:“用 wait() 配合 notifyAll() 就像在微信群里问‘谁会修电脑?’,结果厨师、司机、保洁都冒泡了,最后发现只有 IT 小哥会修。不是消息发错了,而是沟通方式太粗放。当存在多种等待条件时,wait() 就显得过于‘一刀切’了。” 因此,在需要精细控制线程协作的场景下,Condition 才是现代并发编程中的理想选择。
// 检查并处理外卖订单之前,先完成堂食任务
while (takeoutOrders.isEmpty()) {
    System.out.println("厨师:暂无外卖订单,等待外卖员下单...");
    TAKEOUT_COND.await(); // 等待外卖通知信号
}
// 开始制作外卖餐品
String takeoutOrder = takeoutOrders.poll();
System.out.println("厨师:正在处理外卖订单——" + takeoutOrder);
} catch (InterruptedException e) {
    e.printStackTrace();
} finally {
    LOCK.unlock(); // 释放锁,确保异常时也能解锁
}
}, "厨师").start();

// 堂食顾客下单线程(模拟顾客1点餐)
new Thread(() -> {
    LOCK.lock();
    try {
        dineInOrders.add("番茄炒蛋+米饭");
        System.out.println("顾客1:提交了堂食订单——番茄炒蛋+米饭");
        // 仅唤醒与堂食相关的等待线程
        DINE_IN_COND.signal(); // 类似于 notify(),但更精确
    } finally {
        LOCK.unlock();
    }
}, "堂食顾客1").start();

// 外卖员提交订单线程(模拟外卖员1发送订单)
new Thread(() -> {
    LOCK.lock();
    try {
        takeoutOrders.add("黄焖鸡米饭+可乐");
        System.out.println("外卖员1:提交了外卖订单——黄焖鸡米饭+可乐");
        // 仅通知处理外卖的线程
        TAKEOUT_COND.signal(); // 精准唤醒外卖处理逻辑
    } finally {
        LOCK.unlock();
    }
}, "外卖员1").start();
}
}

运行结果(顺序清晰、执行精准):
厨师:暂无堂食订单,正在等待顾客下单...
顾客1:提交了堂食订单——番茄炒蛋+米饭
厨师:正在处理堂食订单——番茄炒蛋+米饭
厨师:暂无外卖订单,等待外卖员下单...
外卖员1:提交了外卖订单——黄焖鸡米饭+可乐
厨师:正在处理外卖订单——黄焖鸡米饭+可乐

“看,现在是先来的堂食先做,后到的外卖后处理,完全不混乱!”王哥笑着说,“Condition 就像是给不同类型的线程划分了独立的工作组,每组都有自己的通信频道。通知的时候只叫对应组的人,其他人不受影响——这比 wait() 那种‘全公司广播’的方式高效太多了。”

三、wait() 与 Condition 的核心差异对比

生活化类比: wait(): 想象学校只有一个公共广播系统,老师说“请班长来办公室”,于是全校所有班级的班长都得跑过去查看,结果发现只找一班班长——其他班长白白浪费时间来回奔波,还耽误本职工作; Condition: 每个班级配备专用对讲机,老师直接呼叫“一班班长来办公室”,只有目标班长收到消息并行动,其余班级继续上课,互不干扰,效率显著提升。 “再举个程序中的例子,”王哥接着解释道,“假如你设计一个‘生产者-消费者’模型,其中生产者分为‘水果类’和‘蔬菜类’,消费者也分两类。如果使用 wait(),一旦唤醒,可能导致水果消费者误入蔬菜队列,反之亦然——造成资源错配和空等现象; 而使用 Condition,则可以分别为水果和蔬菜创建独立的等待条件,实现按类别精准唤醒,彻底避免交叉干扰。”

四、应用场景解析:何时选择 wait()?何时使用 Condition?

王哥喝了一口冰阔落,语气笃定地总结:

4.1 推荐使用 wait() 的情况:单一条件等待场景

适用于逻辑简单、仅需一个同步条件的情境,例如: - 外卖订单催单功能(只需等待“接单”这一事件); - 基础线程协作(如线程A等待线程B完成某项任务后再继续)。 这类场景下,使用 wait() 完全足够,代码简洁,且由 synchronized 自动管理加锁与解锁流程,无需手动控制。 “就像你只需要喊一声‘大家都停下等我’,用一个大喇叭(wait())就够了,没必要每人发一台对讲机。”

4.2 推荐使用 Condition 的情况:多条件分组等待场景

当面临以下复杂需求时,应优先选用 Condition: - 多类型订单并行处理(如区分堂食与外卖、普通客户与VIP订单); - 复杂的生产者-消费者结构(多种生产者与消费者,需按商品或任务类型分组); - 要求精确唤醒特定线程(如高优先级任务优先调度); - 需要获取当前等待队列的状态信息(例如统计有多少外卖订单正在排队)。 “在这些情况下,若仍使用 wait(),很容易导致线程响应错乱,整个系统像一锅乱炖;而 Condition 相当于为线程建立了清晰的组织架构,分工明确,调度有序——这就是专业工具带来的本质区别。”

插个总结:“很多程序员分不清这俩,就像分不清‘一次性筷子’和‘可降解分餐筷’—— 都是筷子,但使用场景不同:日常吃饭图方便用一次性的(wait()),多人分餐、注重秩序与卫生则用分餐筷(Condition);若用 wait() 来处理多个等待条件,就如同拿一次性筷子给十个人分菜,混乱且不安全!”

五、总结:3 句口诀 + 避坑指南

单条件用 wait(),多条件用 Condition: 场景越简单,越不需要引入复杂的工具。就像小问题不必动用重型机械,基础同步手段足够应对单一等待条件。

Condition 必须搭配 Lock 使用: 不要试图将 Condition 与 synchronized 混用,这就像让对讲机连接广播喇叭,两者协议不匹配,根本无法协同工作。

Lock 必须手动释放: 调用 Condition 的 await() 方法并不会自动释放锁。因此,unlock() 一定要放在 finally 块中执行。否则一旦发生异常,锁将无法释放,导致后续线程全部阻塞——这种情况比在同步块外调用 wait() 还严重得多!

5.1 避坑指南(王哥的血泪史)

“我刚开始用 Condition 的时候,把 unlock() 写在了 try 块里面,” 王哥捂脸回忆道,“结果代码中途抛了异常,unlock() 根本没机会执行,锁一直被占用,所有线程都卡死了。我当时以为系统彻底崩溃,还傻乎乎地重启了服务器,结果被领导狠狠批评‘连个锁都解不开’。”

“从那以后,我写 Condition 相关逻辑时,第一件事就是先写下 lock(),然后立刻补上 finally 块里的 unlock(),最后才填充中间的业务代码。现在这套流程我已经熟得像每天摸鱼一样自然了!”

六、最后说句实在的

Condition 更加专业、灵活。今天分享的这两段代码,你拿过去直接运行就能用,只需根据实际需求调整 Condition 的数量以及唤醒的逻辑,就能轻松集成到自己的项目中。

如果你已经理解透彻,不妨在下次团队协作时帮助他人提升效率。比如看到同事还在用 wait() 处理多个等待条件,导致代码混乱不堪,可以在代码 review 时微笑着说:“兄弟,别再用广播喇叭喊人了,给每个线程配个专属对讲机(Condition),让它们各司其职、精准响应,不香吗?”

二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

关键词:condition dition wait con TIO

您需要登录后才可以回帖 登录 | 我要注册

本版微信群
jg-xs1
拉您进交流群
GMT+8, 2025-12-9 09:58