最稳妥的方式是使用BlockingQueue而非手写wait/notify,因其天然线程安全、阻塞语义明确、边界处理完整;手写易出现唤醒丢失、虚假唤醒、未用while循环检查条件及锁粒度不合理等问题。
Java里实现生产者消费者模型,最稳妥的方式不是手写 wait/notify,而是用 BlockingQueue——它天然线程安全、阻塞语义明确、边界处理完整。
wait/notify 手写?手写容易漏掉几个关键点:唤醒丢失(notify 早于 wait)、虚假唤醒(spurious wakeup)、未在循环中检查条件、锁粒度不合理。哪怕代码看着“能跑”,在高并发或压力下极易出现死锁、数据丢失或无限等待。
常见错误现象包括:
BlockingQueue 的三种典型用法选哪种取决于场景对容量、公平性、响应性的要求:
ArrayBlockingQueue:有界、基于数组、构造时必须指定容量;适合内存敏感、需硬性限流的场景(如日志缓冲
LinkedBlockingQueue:默认无界(实际是 Integer.MAX_VALUE),但可传参设界;吞吐通常更高,但无界时可能 OOMSynchronousQueue:不存储元素,每个 put 必须配一个 take;适合任务交接型场景(如线程池的 DirectHandoff)示例:用 ArrayBlockingQueue 实现基础模型
BlockingQueuequeue = new ArrayBlockingQueue<>(10); // 生产者 new Thread(() -> { for (int i = 0; i < 5; i++) { try { queue.put("item-" + i); // 阻塞直到有空位 System.out.println("produced: item-" + i); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }).start(); // 消费者 new Thread(() -> { for (int i = 0; i < 5; i++) { try { String item = queue.take(); // 阻塞直到有数据 System.out.println("consumed: " + item); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }).start();
synchronized + wait/notify,怎么写才不出错?仅当需要自定义等待逻辑(比如超时、多条件组合)且无法用 BlockingQueue 替代时才考虑。核心守则只有三条:
synchronized 块内调用 wait() 和 notify()
while 循环检查条件,不能用 if
wait() 的判断条件,必须使用同一把锁错误写法:if (queue.isEmpty()) wait(); → 可能虚假唤醒后直接取空队列
正确写法:while (queue.isEmpty()) wait();
真正难的不是写出来,而是想清楚「谁负责唤醒」「唤醒是否及时」「中断是否被正确传播」——这些细节在 BlockingQueue 里已被反复验证过,自己重造轮子时最容易在日志里看到 IllegalMonitorStateException 或线程卡死,却查不出哪一行锁没对上。