如何通过 Object.wait() 和 notify() 实现多线程生产者-消费者模型协作?

2026-05-07 20:441阅读0评论SEO基础
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计1088个文字,预计阅读时间需要5分钟。

如何通过 Object.wait() 和 notify() 实现多线程生产者-消费者模型协作?

因为这两个方法必须在同步上下文中执行——也就是说,当前线程必须保持对相应对象的监视器锁。否则,会抛出IllegalMonitorStateException。常见错误是忘记添加synchronized块,或者锁的对象和调用wait()的对象不一致。

正确做法是:所有对共享缓冲区的操作(读/写/等待)都必须在同一个锁对象上同步,通常就是缓冲区实例本身。

  • synchronized(buffer) 包裹所有涉及 wait()/notify() 的逻辑
  • 避免用 this 或新创建的锁对象,否则生产者和消费者无法看到彼此的通知
  • wait() 一定要放在 while 循环里判断条件,不能用 if——防止虚假唤醒

如何用 wait()/notify() 实现带边界检查的阻塞队列

核心是把“缓冲区满”和“缓冲区空”作为两个独立等待条件,通过同一个锁对象协调。

示例中用一个 ArrayList 模拟固定容量队列,生产者在满时 wait(),消费者在空时 wait();任一操作完成后都调用 notifyAll()(不是 notify()),确保不会漏掉等待方。

public class BoundedBuffer { private final List<String> buffer = new ArrayList<>(); private final int capacity = 5; public void produce(String item) throws InterruptedException { synchronized (buffer) { while (buffer.size() == capacity) { buffer.wait(); // 等待有空间 } buffer.add(item); buffer.notifyAll(); // 唤醒可能等待取数据的消费者 } } public String consume() throws InterruptedException { synchronized (buffer) { while (buffer.isEmpty()) { buffer.wait(); // 等待有数据 } String item = buffer.remove(0); buffer.notifyAll(); // 唤醒可能等待放数据的生产者 return item; } } }

  • notifyAll() 而非 notify():避免唤醒错线程类型(比如只唤醒另一个生产者)
  • 容量检查必须用 while:JVM 可能无理由唤醒线程(spurious wakeup),不重检条件会出错
  • 注意 remove(0) 是 O(n) 操作,实际应改用 LinkedList 或数组循环队列

wait(long timeout) 怎么用才安全

带超时的 wait() 不等于“最多等 timeout”,而是“最多等 timeout,但可能提前被唤醒”。所以它不能替代条件判断,仍需配合 while 循环使用。

典型误用是写了 if (buffer.isEmpty()) buffer.wait(1000);,结果超时后直接取数据,没再检查是否真有数据。

  • 超时后必须重新判断条件,否则可能触发 IndexOutOfBoundsException
  • 超时适合做“保底响应”,比如日志告警、降级返回 null,而不是跳过条件检查
  • 不要依赖超时值做业务逻辑分流(如“等不到就走异步路径”),那应该用 Lock.tryLock() + 条件队列

java.util.concurrent 里的实现比,手写有什么硬伤

手写 wait()/notify() 最大的问题是无法分离“等待条件”——所有 wait() 都共用一个等待队列,导致每次 notifyAll() 都要唤醒全部线程,再由它们各自重判条件。这在高并发下浪费严重。

ArrayBlockingQueue 内部用 ReentrantLock + 两个 ConditionnotFull / notEmpty),可以精准唤醒对应等待组,避免无效竞争。

  • 手写版在百级线程规模下,notifyAll() 引发的上下文切换开销会明显上升
  • 没有中断支持:原生 wait() 可被 interrupt() 中断,但需要手动处理 InterruptedException 并恢复中断状态
  • 无法实现公平策略、无法绑定多个等待条件、难以调试死锁点

除非教学或嵌入式受限环境,否则优先用 BlockingQueue 实现。手写只是为了理解底层协作契约——wait 是释放锁并挂起,notify 是唤醒但不抢锁,真正竞争在重新进入同步块时才开始。

标签:AI

本文共计1088个文字,预计阅读时间需要5分钟。

如何通过 Object.wait() 和 notify() 实现多线程生产者-消费者模型协作?

因为这两个方法必须在同步上下文中执行——也就是说,当前线程必须保持对相应对象的监视器锁。否则,会抛出IllegalMonitorStateException。常见错误是忘记添加synchronized块,或者锁的对象和调用wait()的对象不一致。

正确做法是:所有对共享缓冲区的操作(读/写/等待)都必须在同一个锁对象上同步,通常就是缓冲区实例本身。

  • synchronized(buffer) 包裹所有涉及 wait()/notify() 的逻辑
  • 避免用 this 或新创建的锁对象,否则生产者和消费者无法看到彼此的通知
  • wait() 一定要放在 while 循环里判断条件,不能用 if——防止虚假唤醒

如何用 wait()/notify() 实现带边界检查的阻塞队列

核心是把“缓冲区满”和“缓冲区空”作为两个独立等待条件,通过同一个锁对象协调。

示例中用一个 ArrayList 模拟固定容量队列,生产者在满时 wait(),消费者在空时 wait();任一操作完成后都调用 notifyAll()(不是 notify()),确保不会漏掉等待方。

public class BoundedBuffer { private final List<String> buffer = new ArrayList<>(); private final int capacity = 5; public void produce(String item) throws InterruptedException { synchronized (buffer) { while (buffer.size() == capacity) { buffer.wait(); // 等待有空间 } buffer.add(item); buffer.notifyAll(); // 唤醒可能等待取数据的消费者 } } public String consume() throws InterruptedException { synchronized (buffer) { while (buffer.isEmpty()) { buffer.wait(); // 等待有数据 } String item = buffer.remove(0); buffer.notifyAll(); // 唤醒可能等待放数据的生产者 return item; } } }

  • notifyAll() 而非 notify():避免唤醒错线程类型(比如只唤醒另一个生产者)
  • 容量检查必须用 while:JVM 可能无理由唤醒线程(spurious wakeup),不重检条件会出错
  • 注意 remove(0) 是 O(n) 操作,实际应改用 LinkedList 或数组循环队列

wait(long timeout) 怎么用才安全

带超时的 wait() 不等于“最多等 timeout”,而是“最多等 timeout,但可能提前被唤醒”。所以它不能替代条件判断,仍需配合 while 循环使用。

典型误用是写了 if (buffer.isEmpty()) buffer.wait(1000);,结果超时后直接取数据,没再检查是否真有数据。

  • 超时后必须重新判断条件,否则可能触发 IndexOutOfBoundsException
  • 超时适合做“保底响应”,比如日志告警、降级返回 null,而不是跳过条件检查
  • 不要依赖超时值做业务逻辑分流(如“等不到就走异步路径”),那应该用 Lock.tryLock() + 条件队列

java.util.concurrent 里的实现比,手写有什么硬伤

手写 wait()/notify() 最大的问题是无法分离“等待条件”——所有 wait() 都共用一个等待队列,导致每次 notifyAll() 都要唤醒全部线程,再由它们各自重判条件。这在高并发下浪费严重。

ArrayBlockingQueue 内部用 ReentrantLock + 两个 ConditionnotFull / notEmpty),可以精准唤醒对应等待组,避免无效竞争。

  • 手写版在百级线程规模下,notifyAll() 引发的上下文切换开销会明显上升
  • 没有中断支持:原生 wait() 可被 interrupt() 中断,但需要手动处理 InterruptedException 并恢复中断状态
  • 无法实现公平策略、无法绑定多个等待条件、难以调试死锁点

除非教学或嵌入式受限环境,否则优先用 BlockingQueue 实现。手写只是为了理解底层协作契约——wait 是释放锁并挂起,notify 是唤醒但不抢锁,真正竞争在重新进入同步块时才开始。

标签:AI