Java多线程编程:生产者消费者模型与wait/notify机制详解

发布时间:2026/7/19 19:41:15
Java多线程编程:生产者消费者模型与wait/notify机制详解 1. 生产者消费者模型基础解析生产者消费者模型是多线程编程中最经典的并发协作模式之一。想象一下餐厅后厨的工作场景厨师们生产者不断制作菜品服务员们消费者持续将做好的菜品送到顾客桌上。这个模型中最关键的是厨师和服务员之间需要通过一个传菜窗口缓冲区进行协调避免厨师做好的菜无处可放或者服务员无菜可取的尴尬局面。在Java中我们使用Object类的wait()、notify()和notifyAll()方法来实现这种线程间协作。这三个方法都必须放在synchronized同步代码块或同步方法中调用因为它们都需要先获得对象的监视器锁monitor lock。关键理解wait()会使当前线程释放锁并进入等待状态notify()/notifyAll()则会唤醒等待该对象锁的线程。但被唤醒的线程不会立即执行必须等到当前线程退出同步块释放锁后才会真正获得执行机会。2. wait/notify机制深度剖析2.1 方法语义与使用规范wait()方法有三个重要特性调用后立即释放对象锁线程状态转为WAITING被唤醒后需要重新获取锁才能继续执行notify()和notifyAll()的区别在于notify()随机唤醒一个等待线程notifyAll()唤醒所有等待该对象锁的线程// 典型使用模式 synchronized(lock) { while(条件不满足) { // 必须用while而不是if lock.wait(); } // 执行操作 lock.notifyAll(); // 或notify() }2.2 为什么必须使用while循环检查条件初学者常犯的错误是用if代替while检查条件。考虑以下场景缓冲区空消费者A和B都进入等待生产者放入一个数据后调用notifyAll()消费者A被唤醒取走数据消费者B也被唤醒但此时缓冲区已空如果使用if就会导致空指针异常// 错误示范 synchronized(lock) { if(buffer.isEmpty()) { // 应该用while lock.wait(); } buffer.remove(); }3. 四种典型实现场景3.1 单生产者单消费者模型这是最简单的场景不需要考虑线程间的复杂竞争。核心要点生产者检查缓冲区满则wait()消费者检查缓冲区空则wait()每次操作后调用notify()即可// 生产者线程 public void run() { synchronized(lock) { while(buffer.isFull()) { lock.wait(); } buffer.put(item); lock.notify(); // 唤醒消费者 } } // 消费者线程 public void run() { synchronized(lock) { while(buffer.isEmpty()) { lock.wait(); } item buffer.take(); lock.notify(); // 唤醒生产者 } }3.2 单生产者多消费者模型当消费者线程多于一个时必须注意必须使用while循环检查条件应该使用notifyAll()而非notify()考虑性能时可以优化为生产者唤醒所有消费者消费者只唤醒生产者// 消费者改进版 public void run() { synchronized(lock) { while(buffer.isEmpty()) { // 必须用while lock.wait(); } item buffer.take(); lock.notifyAll(); // 必须唤醒所有线程 } }3.3 多生产者单消费者模型与单生产者多消费者对称需要注意生产者之间也需要协调生产者应该唤醒所有线程包括其他生产者和消费者缓冲区操作需要更严格的同步控制3.4 多生产者多消费者模型这是最复杂的场景完整实现需要考虑所有条件检查必须使用while所有唤醒必须使用notifyAll()建议使用单独的锁对象而非缓冲区本身考虑使用更高级的并发容器如BlockingQueueclass Buffer { private final QueueInteger queue new LinkedList(); private final int CAPACITY 10; private final Object lock new Object(); public void put(int item) throws InterruptedException { synchronized(lock) { while(queue.size() CAPACITY) { lock.wait(); } queue.add(item); lock.notifyAll(); } } public int take() throws InterruptedException { synchronized(lock) { while(queue.isEmpty()) { lock.wait(); } int item queue.remove(); lock.notifyAll(); return item; } } }4. 常见问题与性能优化4.1 死锁场景分析典型死锁情况所有生产者wait()消费者也wait()错误使用notify()导致关键线程未被唤醒锁的嵌套使用不当解决方案确保每次操作后都有唤醒调用优先使用notifyAll()避免在持有锁时调用外部方法4.2 性能优化技巧双重检查锁定在wait()前增加一次条件检查分离通知生产者只唤醒消费者消费者只唤醒生产者使用Condition对象替代内置锁更精细的等待/通知控制设置合理的超时时间wait(long timeout)避免永久等待// 使用Condition的优化实现 class OptimizedBuffer { private final Lock lock new ReentrantLock(); private final Condition notFull lock.newCondition(); private final Condition notEmpty lock.newCondition(); public void put(int item) throws InterruptedException { lock.lock(); try { while(queue.size() CAPACITY) { notFull.await(); } queue.add(item); notEmpty.signal(); // 只唤醒消费者 } finally { lock.unlock(); } } }4.3 监控与调试技巧线程转储分析jstack查看线程状态日志记录在关键操作前后添加日志使用可视化工具如JConsole、VisualVM单元测试模拟高并发场景实战经验在复杂场景中建议先用简单的notifyAll()实现功能再逐步优化为更精细的通知机制。过早优化往往是并发bug的根源。5. 现代Java中的替代方案虽然wait/notify是基础但现代Java提供了更高级的替代方案BlockingQueue接口内置线程安全的阻塞队列Executor框架封装了线程池和任务队列CompletableFuture支持异步编程Flow API响应式流处理// 使用BlockingQueue的简化实现 BlockingQueueInteger queue new ArrayBlockingQueue(10); // 生产者 queue.put(item); // 自动阻塞 // 消费者 Integer item queue.take(); // 自动阻塞这些高级API底层仍然基于wait/notify机制但提供了更安全易用的抽象。理解wait/notify的原理有助于我们更好地使用这些高级工具也能在需要定制特殊功能时游刃有余。