You need to enable JavaScript to run this app. Java生产者消费者交替输出异常,加synchronized后无输出的解决方法
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.11.06 01:15:03