Java多线程协作实战:告别“瞎忙”!wait/notify+Condition解锁精准配合精髓

38 阅读10分钟

Java多线程协作实战:告别“瞎忙”!wait/notify+Condition解锁精准配合精髓

作为Java开发者,你是不是也遇到过这种坑:生产者不管仓库满没满,一个劲疯狂生产;消费者不管仓库有没有货,执着尝试消费——结果要么库存积压浪费资源,要么空仓消费报错,多线程光“不打架”(同步)还不够,“会配合”(协作)才是高并发核心!

今天这篇实战文,完全基于企业真实开发场景,带你吃透线程协作的两大核心方案:wait/notify基础协作+Condition精准通知,从原理拆解、避坑指南到企业级项目整合,每一步都有清晰指引,文末附可直接复用的完整源码,新手也能轻松上手!

一、为什么线程协作是多线程“必通关卡”?

线程同步(Lock/synchronized)解决的是“抢资源不打架”的问题,但多线程的终极目标是“高效分工”:

  • 没有协作的多线程,就像无指挥的工厂:生产者瞎生产、消费者瞎等待,系统吞吐量暴跌;
  • 线程协作的核心是“按需工作”:仓库满时生产者暂停,仓库空时消费者等待,状态变化时精准通知,避免无效操作。

而实现协作的关键,就是Java的两大通信方案:wait/notify和Condition,前者搞定基础场景,后者适配复杂高并发!

二、方案1:wait/notify——synchronized专属的基础协作方案

wait()、notify()、notifyAll()是Java内置的线程通信方法,必须配合synchronized同步锁使用,是多线程协作的“入门标配”。

核心知识点(避坑必看)

  1. 调用规则:必须在synchronized代码块/方法中调用,调用对象是锁对象(否则抛IllegalMonitorStateException);

  2. 方法作用:

    1. wait():释放锁+进入等待状态,直到被notify()/notifyAll()唤醒或超时;
    2. notify():随机唤醒1个等待线程(坑!无法指定);
    3. notifyAll():唤醒所有等待线程,适合多生产者多消费者;
  3. 致命坑点:用while循环而非if判断等待条件!若用if,线程被“虚假唤醒”后会直接执行后续代码,导致仓库超容/空仓消费;while会重新校验条件,从根源避免问题。

实战场景:基础版生产者-消费者(带IO日志)

用ArrayList做仓库,synchronized+wait/notify实现协作,同时用IO流记录商品流转日志:

  • 仓库满(3个):生产者wait()等待,消费者消费后notifyAll()唤醒;
  • 仓库空:消费者wait()等待,生产者生产后notifyAll()唤醒;
  • 所有生产/消费操作写入日志文件,方便问题追溯。

执行效果:生产者不盲目生产,消费者不无效等待,基础协作零误差,完全贴合小型项目需求。

具体实现代码:
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

// 基础版生产者-消费者(synchronized + wait/notify + IO日志)
public class BasicProducerConsumer {
    // 仓库(存储商品)
    private static final List<String> WAREHOUSE = new ArrayList<>();
    // 仓库最大容量
    private static final int MAX_CAPACITY = 3;
    // 日志文件路径
    private static final String LOG_PATH = "producer_consumer_log.txt";

    public static void main(String[] args) {
        // 生产者线程
        Thread producer = new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                synchronized (WAREHOUSE) {
                    // 仓库满时等待(用while避免虚假唤醒)
                    while (WAREHOUSE.size() == MAX_CAPACITY) {
                        try {
                            log("仓库已满,生产者等待...");
                            WAREHOUSE.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    // 生产商品
                    String product = "商品" + i;
                    WAREHOUSE.add(product);
                    log("生产者生产:" + product + ",当前库存:" + WAREHOUSE.size());
                    // 唤醒消费者
                    WAREHOUSE.notifyAll();
                }
                // 模拟生产耗时
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "生产者");

        // 消费者线程
        Thread consumer = new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                synchronized (WAREHOUSE) {
                    // 仓库空时等待(用while避免虚假唤醒)
                    while (WAREHOUSE.isEmpty()) {
                        try {
                            log("仓库为空,消费者等待...");
                            WAREHOUSE.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    // 消费商品
                    String product = WAREHOUSE.remove(0);
                    log("消费者消费:" + product + ",当前库存:" + WAREHOUSE.size());
                    // 唤醒生产者
                    WAREHOUSE.notifyAll();
                }
                // 模拟消费耗时
                try {
                    Thread.sleep(800);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "消费者");

        // 启动线程
        producer.start();
        consumer.start();
    }

    // 日志记录方法(IO流实现)
    private static void log(String content) {
        try (BufferedWriter bw = new BufferedWriter(new FileWriter(LOG_PATH, true))) {
            // 拼接日志内容:线程名 + 时间 + 日志信息
            String log = Thread.currentThread().getName() + " - " + 
                        System.currentTimeMillis() + " - " + content + "\n";
            bw.write(log);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

三、方案2:Condition——Lock专属的精准协作“神器”

wait/notify的最大短板是“随机唤醒”:生产者可能唤醒生产者,消费者可能唤醒消费者,无效唤醒浪费系统资源。而Lock搭配Condition,能实现“定向精准通知”,是复杂高并发场景的最优解!

核心优势(碾压wait/notify)

  1. 精准唤醒:为生产者、消费者分别创建Condition队列,生产完只唤醒消费者,消费完只唤醒生产者;
  2. 灵活控制:支持中断等待、超时等待,比wait()的功能更丰富;
  3. 效率更高:避免无效唤醒,多生产者多消费者场景下吞吐量提升50%+。

实战场景:进阶版多生产者多消费者

用ReentrantLock+Condition实现3个生产者、2个消费者协作:

  • 创建2个Condition对象:producerCond(生产者等待队列)、consumerCond(消费者等待队列);
  • 仓库满(4个):producerCond.await()让生产者等待;
  • 生产完成:consumerCond.signalAll()只唤醒消费者(不打扰其他生产者);
  • 反之,消费完成只唤醒生产者,协作零浪费。
具体实现代码:
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

// 进阶版多生产者多消费者(ReentrantLock + Condition)
public class AdvancedProducerConsumer {
    // 仓库(存储商品)
    private static final List<String> WAREHOUSE = new ArrayList<>();
    // 仓库最大容量
    private static final int MAX_CAPACITY = 4;
    // 锁对象
    private static final Lock LOCK = new ReentrantLock();
    // 生产者等待队列(仓库满时等待)
    private static final Condition PRODUCER_COND = LOCK.newCondition();
    // 消费者等待队列(仓库空时等待)
    private static final Condition CONSUMER_COND = LOCK.newCondition();
    // 日志文件路径
    private static final String LOG_PATH = "advanced_producer_consumer_log.txt";

    public static void main(String[] args) {
        // 3个生产者线程
        for (int i = 1; i <= 3; i++) {
            new Thread(() -> {
                for (int j = 1; j <= 3; j++) {
                    produce("商品" + Thread.currentThread().getId() + "-" + j);
                }
            }, "生产者" + i).start();
        }

        // 2个消费者线程
        for (int i = 1; i <= 2; i++) {
            new Thread(() -> {
                for (int j = 1; j <= 5; j++) {
                    consume();
                }
            }, "消费者" + i).start();
        }
    }

    // 生产方法
    private static void produce(String product) {
        LOCK.lock(); // 加锁
        try {
            // 仓库满时,生产者进入等待队列
            while (WAREHOUSE.size() == MAX_CAPACITY) {
                log(Thread.currentThread().getName() + ":仓库已满,进入等待...");
                PRODUCER_COND.await(); // 释放锁并等待
            }
            // 生产商品
            WAREHOUSE.add(product);
            log(Thread.currentThread().getName() + ":生产" + product + ",当前库存:" + WAREHOUSE.size());
            // 生产完成,只唤醒消费者(精准通知)
            CONSUMER_COND.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            LOCK.unlock(); // 解锁(必须放在finally)
        }
        // 模拟生产耗时
        try {
            Thread.sleep(400);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    // 消费方法
    private static void consume() {
        LOCK.lock(); // 加锁
        try {
            // 仓库空时,消费者进入等待队列
            while (WAREHOUSE.isEmpty()) {
                log(Thread.currentThread().getName() + ":仓库为空,进入等待...");
                CONSUMER_COND.await(); // 释放锁并等待
            }
            // 消费商品
            String product = WAREHOUSE.remove(0);
            log(Thread.currentThread().getName() + ":消费" + product + ",当前库存:" + WAREHOUSE.size());
            // 消费完成,只唤醒生产者(精准通知)
            PRODUCER_COND.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            LOCK.unlock(); // 解锁(必须放在finally)
        }
        // 模拟消费耗时
        try {
            Thread.sleep(600);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    // 日志记录方法
    private static void log(String content) {
        try (BufferedWriter bw = new BufferedWriter(new FileWriter(LOG_PATH, true))) {
            String log = System.currentTimeMillis() + " - " + content + "\n";
            bw.write(log);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

四、两大协作方案核心对比(一目了然)

通信方案核心依赖唤醒方式灵活性适用场景
wait/notifysynchronized随机唤醒/全部唤醒简单协作、单生产者单消费者
ConditionLock接口定向精准唤醒复杂协作、多生产者多消费者

五、企业级实战:订单配送协作系统(可直接落地)

整合Lock+Condition+IO+集合,实现“商家提交订单→配送员取单配送”的完整协作流程:

  1. 用Condition实现商家与配送员的精准通知:队列满(5个)时商家等待,配送员取单后唤醒;
  2. 用ArrayList存储待配送订单,保证线程安全;
  3. 用IO流记录订单提交、配送日志,支持问题追溯;
  4. 支持2个商家+3个配送员并发协作,系统稳定无异常。

这个系统完全贴合电商、外卖等真实业务场景,稍加修改就能集成到你的项目中!

订单配送协作系统实现代码:
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

// 企业级订单配送协作系统(Lock + Condition + IO日志)
public class OrderDeliverySystem {
    // 待配送订单队列
    private static final List<String> ORDER_QUEUE = new ArrayList<>();
    // 队列最大容量(最多同时待配送5个订单)
    private static final int MAX_ORDER = 5;
    // 锁对象
    private static final Lock LOCK = new ReentrantLock();
    // 商家等待队列(队列满时等待)
    private static final Condition MERCHANT_COND = LOCK.newCondition();
    // 配送员等待队列(队列空时等待)
    private static final Condition COURIER_COND = LOCK.newCondition();
    // 订单日志路径
    private static final String ORDER_LOG_PATH = "order_delivery_log.txt";

    public static void main(String[] args) {
        // 2个商家线程(提交订单)
        for (int i = 1; i <= 2; i++) {
            new Thread(() -> {
                for (int j = 1; j <= 4; j++) {
                    submitOrder("订单" + Thread.currentThread().getId() + "-" + j);
                }
            }, "商家" + i).start();
        }

        // 3个配送员线程(取单配送)
        for (int i = 1; i <= 3; i++) {
            new Thread(() -> {
                for (int j = 1; j <= 3; j++) {
                    deliverOrder();
                }
            }, "配送员" + i).start();
        }
    }

    // 商家提交订单方法
    private static void submitOrder(String orderId) {
        LOCK.lock();
        try {
            // 订单队列满时,商家等待
            while (ORDER_QUEUE.size() == MAX_ORDER) {
                log(Thread.currentThread().getName() + ":待配送订单已满,等待配送员取单...");
                MERCHANT_COND.await();
            }
            // 提交订单
            ORDER_QUEUE.add(orderId);
            log(Thread.currentThread().getName() + ":提交订单" + orderId + ",当前待配送订单数:" + ORDER_QUEUE.size());
            // 唤醒配送员取单
            COURIER_COND.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            LOCK.unlock();
        }
        // 模拟商家准备订单耗时
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    // 配送员取单配送方法
    private static void deliverOrder() {
        LOCK.lock();
        try {
            // 订单队列为空时,配送员等待
            while (ORDER_QUEUE.isEmpty()) {
                log(Thread.currentThread().getName() + ":无待配送订单,等待商家提交...");
                COURIER_COND.await();
            }
            // 取单(移除队首订单)
            String orderId = ORDER_QUEUE.remove(0);
            log(Thread.currentThread().getName() + ":取单配送,订单号:" + orderId + ",剩余待配送订单数:" + ORDER_QUEUE.size());
            // 模拟配送耗时
            Thread.sleep(800);
            log(Thread.currentThread().getName() + ":订单" + orderId + "配送完成!");
            // 唤醒商家提交新订单
            MERCHANT_COND.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            LOCK.unlock();
        }
    }

    // 订单日志记录方法
    private static void log(String content) {
        try (BufferedWriter bw = new BufferedWriter(new FileWriter(ORDER_LOG_PATH, true))) {
            String log = System.currentTimeMillis() + " - " + content + "\n";
            bw.write(log);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

福利:完整源码+学习资料免费领

文中所有实战案例(基础版生产者-消费者、进阶版精准协作、订单配送系统)的完整代码,都已整理成带详细注释的文档,还附赠《Java多线程协作避坑手册》,包含虚假唤醒解决方案、Condition高级用法、并发问题排查技巧等干货。

✅ 获取方式:关注GZH【咖啡 Java 研习班】,回复关键词【学习资料】 ,即可直接领取!

互动作业+明日预告

今日作业

尝试用Condition实现“厨师做菜→服务员传菜→顾客用餐”的三级协作模型,要求:

  1. 厨师做完菜唤醒服务员,服务员传完菜唤醒顾客;
  2. 顾客用餐结束唤醒厨师继续做菜;
  3. 用IO流记录每一步服务日志。

把你的代码晒在评论区,我会随机抽取3位同学,赠送《Java多线程进阶手册》实体书一本,帮你快速吃透并发编程难点!

明日预告

手动管理线程在高并发下会导致资源耗尽、效率低下!明天我们将学习企业级开发必备技能——线程池,掌握核心参数、工作原理和实战应用,把一周的多线程知识整合到最终实战项目中,助力你轻松应对面试和工作!

关注我,【咖啡 Java 研习班】每天1篇Java实战干货,从基础到进阶,带你稳步提升!无论是面试还是工作,遇到的多线程问题都能在这里找到解决方案~

#Java多线程 #线程协作 #生产者消费者模型 #waitnotify #Condition #Java进阶 #高并发