线程同步问题:生产者消费者代码意外终止而非无限运行
你的生产者消费者程序意外终止的原因及修复方案
首先,你的思路方向是对的,但代码里有两个关键问题导致程序无法无限运行,反而看起来“终止”了:
问题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
相关产品推荐
相关产品推荐

