生产者消费者问题中消费者获旧值原因及线程对象疑问
问题描述
代码实现
class Q2 { int n; boolean valueSet = false; synchronized int get() { while(!valueSet) try { System.out.println("Consumer waiting ..."); wait(); } catch(InterruptedException e) { System.err.println("InterruptedException caught"); } System.out.println("Consumer awakened"); System.out.println("Got: "+n); valueSet = false; notify(); System.out.println("Consumer called notify()"); return n; } synchronized void put(int n) { while(valueSet) try { System.out.println("Producer waiting ..."); wait(); } catch(InterruptedException e) { System.err.println("InterruptedException caught"); } System.out.println("Producer awakened"); System.out.println("Before put n is: " + this.n); this.n = n; valueSet = true; System.out.println("Put: " + this.n); notify(); System.out.println("Producer called notify()"); } } class Producer2 implements Runnable { Q2 q; int noOfTimes; Producer2(Q2 q) { this.q = q; new Thread(this, "Producer").start(); } public void run() { int i=0; noOfTimes=0; while(q.n < 2) { q.put(i++); noOfTimes++; } System.out.println("Producer ran: " + noOfTimes + " times."); } } class Consumer2 implements Runnable { Q2 q; int noOfTimes; Consumer2(Q2 q) { this.q = q; new Thread(this, "Consumer").start(); } public void run() { int i=0; noOfTimes=0; while(q.n < 2) { System.out.println("Iteration " + (noOfTimes+1) + "; Before get() n is: " + q.n); int val = q.get(); System.out.println("After get() n is: " + q.n); noOfTimes++; } System.out.println("Consumer ran: " + noOfTimes + " times."); System.out.println("n: " + q.n); } } public class PCFixed { public static void main(String[] args) { Q2 q = new Q2(); new Producer2(q); new Consumer2(q); } }
某次运行输出
Producer awakened Before put n is: 0 // because n is int and default value of n is 0 Put: 0 // instance variable n associated with object of Q2 is set to 0 Producer called notify() // no effect Producer waiting ... // thread spawned from Producer waits Iteration 1; Before get() n is: 0 // context switching to thread spawned from Consumer Consumer awakened Got: 0 Consumer called notify() After get() n is: 0 Iteration 2; Before get() n is: 0 Producer awakened // context switching to Producer's thread Before put n is: 0 Put: 1 Producer called notify() Producer waiting ... // Producer's thread waits/sleeps Consumer awakened // context switching to Consumer's thread Got: 1 Consumer called notify() After get() n is: 1 Producer awakened // context switching to Producer's thread Before put n is: 1 Put: 2 // #### line 1 Producer called notify() Iteration 3; Before get() n is: 1 // #### line 2: context switching to Consumer's thread Producer ran: 3 times. // context switching to Producer's thread Consumer awakened // context switching to Consumer's thread Got: 2 Consumer called notify() After get() n is: 2 Consumer ran: 3 times. n: 2 // Consumer's thread ends here
疑问
在上述输出中,生产者已在line1处将共享Q2实例的n值设为2,但消费者在line2处获取到的n值却为1。由于生产者与消费者线程共享同一个Q2实例,为何修改后的n值未在line2处及时体现?同时请解释threaded object的含义。若首次运行未出现该现象,多次运行即可复现该问题。
解答
1. 为什么n值未及时体现?
核心原因是线程可见性缺失和非同步的变量访问:
- Q2类的
n变量既没有用volatile修饰,消费者线程在run()方法的while(q.n < 2)循环、以及打印语句中访问n时,也没有进入同步块/方法。 - Java内存模型中,每个线程会缓存共享变量的副本到自己的工作内存。如果没有同步机制(
synchronized或volatile),生产者线程修改主内存中n的值后,不会立即同步到消费者线程的工作内存,导致消费者读取到旧的缓存值(1)。 - 另外,消费者的
while(q.n < 2)循环条件没有同步保护,即使生产者已经把n改成2,消费者线程可能还在使用缓存的旧值,继续进入循环执行打印操作,从而输出1。
简单来说:你在非同步场景下直接读取共享变量q.n,无法保证能看到其他线程对该变量的最新修改,因此出现了旧值。
2. Threaded Object的含义
Threaded Object指的是被多个线程共享、用于线程间交互的对象,是线程间传递数据或完成同步的核心载体。在你的代码中,Q2的实例q就是典型的threaded object:
- 它被生产者和消费者线程共同持有,两个线程通过调用它的
synchronized方法(put、get)完成数据传递和等待唤醒的同步逻辑。 - 这类对象通常包含共享状态(比如代码中的
n和valueSet),并依赖同步机制保证多线程访问时的安全性和状态一致性。
内容的提问来源于stack exchange,提问作者Shri
相关产品推荐
相关产品推荐

