Java生产者消费者交替输出异常,加synchronized后无输出的解决方法
生产者消费者交替输出问题排查与实现方案
一、原代码问题定位
你的代码之所以无法达到交替输出效果、甚至出现“无输出”的假象,核心问题有3个:
- 方法职责与队列逻辑错位:
consume()方法实际在执行“往队列存数据”的生产动作,produce()方法实际在执行“从队列取数据”的消费动作,完全颠倒了设计逻辑 - 仅靠
synchronized无法控制执行顺序:synchronized只保证同一时间只有一个线程执行同步代码,无法保证线程的交替执行权,刚释放锁的线程会优先重新竞争到锁,导致consumer线程连续执行完所有打印,producer线程完全抢不到执行权 - 缺少顺序状态控制:要实现严格的“一次consumer打印、一次produce打印”交替,必须有共享状态标记明确当前允许哪个线程执行,仅靠队列的线程安全机制无法控制打印顺序
- 所谓“完全无输出”实际是控制台输出缓冲未及时刷新,或者producer线程因队列空永久阻塞导致程序挂起,并非真的没有执行逻辑
二、基于wait/notify的基础修正方案
如果用Java内置锁的wait/notify机制实现,核心是新增共享状态标记控制执行权,修正后的完整代码如下:
public class ProducerAndConsumer { // 共享状态:true表示当前轮到consumer执行,false表示轮到produce执行 private volatile boolean turnToConsumer = true; // 共享的当前打印序号 private int currentN = 0; // 锁对象,用于wait/notify通信 private final Object lock = new Object(); // consumer线程调用:打印consumer N public void consume() throws InterruptedException { synchronized (lock) { // 轮询等待执行权,避免虚假唤醒 while (!turnToConsumer) { lock.wait(); } currentN++; System.out.println("consumer " + currentN); // 切换执行权给produce turnToConsumer = false; lock.notifyAll(); } } // produce线程调用:打印produce N public void produce() throws InterruptedException { synchronized (lock) { while (turnToConsumer) { lock.wait(); } System.out.println("produce " + currentN); // 切换执行权给consumer turnToConsumer = true; lock.notifyAll(); } } public static void main(String[] args) throws InterruptedException { ProducerAndConsumer pc = new ProducerAndConsumer(); // 定义要执行的交替轮次,此处执行10轮 int totalRounds = 10; // consumer线程循环执行打印 new Thread(() -> { try { for (int i = 0; i < totalRounds; i++) { pc.consume(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }).start(); // produce线程循环执行打印 new Thread(() -> { try { for (int i = 0; i < totalRounds; i++) { pc.produce(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }).start(); } }
运行后输出完全符合consumer 1 → produce 1 → consumer 2 → produce 2的交替要求。
三、是否可以不用手动编写wait/notify?
完全可以,不需要手动调用Object.wait()/Object.notify(),有多种更简洁的成熟方案可以实现相同效果,常见的有2种:
1. 用容量为1的ArrayBlockingQueue实现
利用队列的阻塞特性天然实现交替:队列容量为1时,consumer打印完后往队列存数据的操作会在队列满时阻塞,produce从队列取完数据后队列空,consumer才能继续存下一个数据,完全不需要手动控制顺序。
核心实现逻辑:
public class AlternatePrinter { // 容量设为1,天然实现一次存一次取的交替 private final ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(1); private int currentN = 0; public void consume() throws InterruptedException { currentN++; System.out.println("consumer " + currentN); // 队列满则阻塞,直到produce取走数据 queue.put(currentN); } public void produce() throws InterruptedException { // 队列空则阻塞,直到consumer存入数据 int n = queue.take(); System.out.println("produce " + n); } // main方法逻辑和之前一致,两个线程分别循环调用consume和produce即可 }
这里的阻塞逻辑是ArrayBlockingQueue内部用
Lock和Condition封装实现的,你不需要手动编写wait/notify相关代码。
2. 用Semaphore(信号量)实现
用两个信号量分别控制两个线程的执行权限:初始给consumer线程的信号量1个许可,produce线程的信号量0个许可。consumer执行完释放produce的许可,produce执行完释放consumer的许可,天然实现交替。
核心实现逻辑:
public class AlternatePrinter { // 初始consumer可执行,给1个许可;produce初始不可执行,给0个许可 private final Semaphore consumerSem = new Semaphore(1); private final Semaphore produceSem = new Semaphore(0); private int currentN = 0; public void consume() throws InterruptedException { consumerSem.acquire(); // 申请consumer的执行许可,无许可则阻塞 currentN++; System.out.println("consumer " + currentN); produceSem.release(); // 释放produce的许可,允许produce执行 } public void produce() throws InterruptedException { produceSem.acquire(); // 申请produce的执行许可 System.out.println("produce " + currentN); consumerSem.release(); // 释放consumer的许可,进入下一轮交替 } }
该方案代码更简洁,顺序控制更直观,完全不需要接触wait/notify方法。
内容的提问来源于stack exchange,提问作者Vikash Srivasatva
相关产品推荐
相关产品推荐

