You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

线程同步问题:生产者消费者代码意外终止而非无限运行

你的生产者消费者程序意外终止的原因及修复方案

首先,你的思路方向是对的,但代码里有两个关键问题导致程序无法无限运行,反而看起来“终止”了:

问题1:内存可见性缺失——Ready变量未正确同步

你的Ready实例变量既没有被volatile修饰,也没有在同步方法/块中被访问。Java中线程会缓存共享变量的副本,这就会导致:

  • 生产者设置Ready=true后,消费者可能一直看不到这个更新,持续检查q.isReady()得到false,陷入空转;
  • 反过来,消费者设置Ready=false后,生产者也可能看不到,一直认为Ready是true,不会执行新的put操作。

最终两个线程都在无意义的循环里空转,不再输出任何内容,看起来就像程序终止了。

问题2:竞态条件风险

虽然put和get加了synchronized,但isReady()和setReady()没有同步保护。比如,生产者刚通过!q.isReady()的检查,准备执行put时,消费者可能刚好把Ready设为false,这时候生产者还是会执行put,覆盖之前的值,导致消费者可能跳过某个数字——这不是程序“终止”的核心原因,但会导致逻辑异常。


修复方案1:修复标志位实现(添加volatile和同步)

我们可以给Ready加上volatile关键字保证内存可见性,同时把isReady()和setReady()改成同步方法,避免竞态条件:

public class Q {
    int n;
    volatile boolean Ready; // 添加volatile保证跨线程可见性

    public Q() {
        Ready = false;
    }

    public synchronized void setReady(boolean ready) { // 同步方法保护修改
        Ready = ready;
    }

    public synchronized boolean isReady() { // 同步方法保护读取
        return Ready;
    }

    synchronized void put(int n) {
        this.n = n;
        System.out.println("PUT:" + n);
    }

    synchronized int get() {
        System.out.println("GOT:" + n);
        return n;
    }
}

// Producer和Consumer类无需修改,逻辑依赖同步后的isReady/setReady

不过这种方式还是忙等待,会持续占用CPU资源,不如经典的wait()/notify()方案高效。


修复方案2:使用wait()/notify()的经典实现(更优雅高效)

这也是你老师演示的方式,通过等待-通知机制让线程在无任务时休眠,避免空转,同时完美保证同步:

public class Q {
    int n;
    boolean hasValue = false;

    synchronized void put(int n) {
        // 如果已有值,等待消费者取走
        while (hasValue) {
            try {
                wait(); // 释放锁,进入休眠等待状态
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return;
            }
        }
        this.n = n;
        System.out.println("PUT:" + n);
        hasValue = true;
        notify(); // 唤醒等待的消费者线程
    }

    synchronized int get() {
        // 如果无值,等待生产者放入
        while (!hasValue) {
            try {
                wait();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return -1;
            }
        }
        System.out.println("GOT:" + n);
        hasValue = false;
        notify(); // 唤醒等待的生产者线程
        return n;
    }
}

// 简化后的Producer类,无需手动检查标志位
public class Producer extends Thread {
    Q q;

    public Producer(Q q) {
        this.q = q;
    }

    public void run() {
        int i = 0;
        for (;;) {
            q.put(i++);
            // 可选:加小延迟方便观察输出,不加也不影响逻辑
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
    }
}

// 简化后的Consumer类
public class Consumer extends Thread {
    Q q;

    public Consumer(Q q) {
        this.q = q;
    }

    public void run() {
        for (;;) {
            q.get();
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
    }
}

这个版本的优势很明显:

  • 线程在无任务时会休眠,不会浪费CPU资源;
  • 用synchronized+wait()/notify()保证了同步和内存可见性,彻底避免竞态条件;
  • 逻辑更简洁,完全符合经典的生产者消费者模型。

最后补充:你说的“程序意外终止”,本质上是线程陷入无意义的空转,不再执行输出操作——JVM本身并没有终止(除非你把线程设为守护线程,main线程结束后JVM才会退出)。用上面的修复方案就能让程序正常无限循环运行。

内容的提问来源于stack exchange,提问作者Chaitanya Chavali

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 09:21:29