新手也能学会的 Java Condition,生活示例,轻松搞定线程协作

个人观点,仅供参考!

        作为刚入门 Java 并发编程的小伙伴,你是不是也曾对其抓耳挠腮?明明想让线程 “等条件满足再干活”,结果要么唤醒错对象,要么线程稀里糊涂被叫醒?今天咱们聊的Contidion接口,就是解决这个痛点的 “并发协作神器”——只有满足指定条件,线程才会被精准唤醒干活

一、先搞懂Condition 到底是个啥?

        Condition 是 Java 并发编程里的 “高级等待 / 唤醒工具”,其实就是一个“条件等待器”,在“拿着锁”的前提下(因为Condition依赖 Lock 锁实现),规定线程“什么时候该等、谁来叫醒”的一套规则。它比传统的 synchronized + wait ()/notify () 更灵活。

先不要急着看源码,我们先从奶茶店点单场景入手:

二、Condition 的 工作流程,像极了奶茶店点餐到取餐

1、奶茶店的"取号规则":没锁就没资格等

        你去一家网红奶茶店点单,店里有个明确规定,必须先排队取号(拿到锁 Lock),才能进入后续流程。这对应并发编程里的核心约束:Condition 必须依赖 Lock 存在。没拿号(没锁)的人,连"等奶茶"的资格都没有——直接进店喊"我的奶茶呢",只会被保安请出去(抛出 IllegalMonitorStateException)。

2、等待逻辑:条件不满足,找个座位歇着

        点完单后,你拿到取餐码 "32 号",此时奶茶还没做好(条件不满足)。你总不能堵在柜台前催单吧?那样会影响后面的人点单。于是店员指引你去 30-39 号等待区 坐着等吧。

这个过程对应 await()的三个关键动作:

动作奶茶店场景线程行为
进入等待区你坐到 30-39 号区域线程进入 Condition 的等待队列
交出排队号你的取号牌暂时失效释放 Lock 锁
等待休息线程阻塞,不消耗 CPU

这就是 Condition "等待",条件不满足时,让线程暂时"歇着",还不耽误别人干活。

3 、唤醒逻辑:条件满足,精准叫号

当店员喊32 号取餐你听到后起身,但注意——不是直接拿到奶茶,而是:

这个过程就对应signal() 的行为。注意:signal() 是"叫你起来排队",不是"瞬移到柜台完成取餐"。被唤醒 ≠ 立刻执行,还得重新抢锁!

三、与传统 wait()和notify()比较

        1、传统的synchronized + wait()和notify()就像一家陋奶茶店只有一个等待区,不管你是普通订单、加料订单还是外卖订单,都挤在一起。店员做好一杯奶茶,只能 随便喊一个人notify()或者notifyAll()所有人.

       2、 而 Condition按条件分等待区,做好哪类订单,就喊哪个区。唤醒精准,无效唤醒降到最低。

特性synchronized + wait/notifyLock + Condition
等待队列数量只有 1 个(所有线程混在一起)

可创建多个(按条件分队列)

唤醒精度随机唤醒 1 个 / 唤醒所有精准唤醒指定条件的线程
使用灵活性低(依赖对象监视器)高(可搭配 Lock 灵活控制)

四、关键细节,新手必避坑

1、必须"循环检查条件"。

        在此之前我们要先了解虚假唤醒,比如奶茶店的喊号机故障,没做好你的奶茶,却误喊了 “32 号取餐”;或者店员喊的是 “22 号”,你听岔了。这就是并发里的 虚假唤醒 —— 线程没被 signal () 唤醒,也可能自己醒过来(JVM 底层机制导致,比如系统中断、线程调度)。

①if检查,踩坑的错误写法

if (milkTeas.size() == 0) {
    System.out.println("奶茶卖光了,顾客等一等~");
    notEmpty.await();
}

②while循环,安全的正确写法

while (milkTeas.size() == 0) {
    System.out.println("奶茶卖光了,顾客等一等~");
    notEmpty.await();
}

2、condition必须在 Lock 保护下调用

        必须在Lock保护下调用,没lock()就调await()和signal(),会直接抛IlegalMonitorStateException的错误,记住“先拿号,再等叫号”!

3、死锁风险

        唤醒线程前一定要修改条件!比如店员喊了取餐码,结果奶茶还没做好,顾客过来又得等,可能导致所有线程都卡住。

4、别漏了 finally 释放锁

        不管线程是正常执行还是抛异常,都要在finallylock.unlock()。

5、 lock() 通常放在 try 外面

        因为原始异常(lock()抛出的,比如线程被中断、JVM 内部错误)会被finally中的异常覆盖,这样会导致 “拿锁失败” 时,也执行 unlock (),触发致命异常。记住只有 成功拿到锁”的线程,才有资格释放锁。

五、典型场景Condition 实现 “生产者 - 消费者”

        光看理论还不够,我们继续用 “奶茶店” 场景实战一把。彻底搞懂 Condition 怎么解决 “制作员做奶茶、顾客买奶茶” 的协作问题 —— 奶茶库存满了制作员就得等,库存空了顾客就得等,用 Condition 精准唤醒对应角色。

import java.util.LinkedList;
import java.util.concurrent.locks.*;

// 奶茶店(最多5杯库存,新手易懂版)
public class MilkTeaShop {
    private final Lock lock = new ReentrantLock();
    private final Condition notFull = lock.newCondition();  // 库存未满
    private final Condition notEmpty = lock.newCondition(); // 库存非空
    private final LinkedList<String> milkTeas = new LinkedList<>();
    private static final int MAX = 5; // 最大库存5杯

    // 制作奶茶(生产者)
    public void makeTea(String tea) throws InterruptedException {
        lock.lock();
        try {
            while (milkTeas.size() == MAX) {
                System.out.println("奶茶满了,制作员等一等~");
                notFull.await();
            }
            milkTeas.add(tea);
            System.out.println("做好:" + tea + ",库存:" + milkTeas.size());
            notEmpty.signal(); // 喊顾客来买
        } finally { lock.unlock(); }
    }

    // 购买奶茶(消费者)
    public String buyTea() throws InterruptedException {
        lock.lock();
        try {
            while (milkTeas.size() == 0) {
                System.out.println("奶茶卖光了,顾客等一等~");
                notEmpty.await();
            }
            String tea = milkTeas.removeFirst();
            System.out.println("买走:" + tea + ",库存:" + milkTeas.size());
            notFull.signal(); // 喊制作员做茶
            return tea;
        } finally { lock.unlock(); }
    }

    //1制作员+2顾客
    public static void main(String[] args) {
        MilkTeaShop shop = new MilkTeaShop();

        // 制作员:做8杯珍珠奶茶
        new Thread(() -> {
            for (int i = 1; i <= 8; i++) {
                try { shop.makeTea("珍珠奶茶" + i); Thread.sleep(500); }
                catch (InterruptedException e) { Thread.currentThread().interrupt(); }
            }
        }).start();

        // 顾客A:买4杯
        new Thread(() -> {
            for (int i = 1; i <= 4; i++) {
                try { shop.buyTea(); Thread.sleep(800); }
                catch (InterruptedException e) { Thread.currentThread().interrupt(); }
            }
        }).start();

        // 顾客B:买4杯
        new Thread(() -> {
            for (int i = 1; i <= 4; i++) {
                try { shop.buyTea(); Thread.sleep(1000); }
                catch (InterruptedException e) { Thread.currentThread().interrupt(); }
            }
        }).start();
    }
}

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐加关注🔔,感谢你的阅读

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值