Integer值未刷新:双线程wait/notify打印奇偶异常排查
问题分析与解决方案
嘿,这个问题的核心其实出在Integer的不可变性和你对变量引用的处理上,咱们一步步拆解清楚:
为什么线程看不到更新后的i?
- Integer是不可变类:
++i这个操作看起来是修改i的值,但实际上对于Integer对象来说,它会创建一个新的Integer实例来保存递增后的值,而不是修改原来的对象本身。 - Runner类里的i是独立的旧引用:你在创建
EvenRunner和OddRunner时,把当时的静态i(值为1的Integer对象)传给了构造函数,这两个Runner类里的i成员变量保存的是这个旧对象的引用。之后静态的volatile i虽然会指向新的Integer对象,但Runner里的i根本没跟着变,还是死死盯着原来那个值为1的对象,自然看不到更新!
你加的volatile修饰的是EvenOdd类的静态i,但Runner里的i和它已经是两个独立的引用了,volatile的可见性保障根本覆盖不到这里。
怎么修复这个问题?
有两种简单的修复方式,选哪种都可以:
方式一:直接访问静态变量
去掉EvenRunner和OddRunner里的i成员变量,直接在run方法里访问EvenOdd.i,这样每次都会取到最新的静态volatile变量引用:
修改后的EvenRunner示例:
class EvenRunner implements Runnable{ Object lck; Integer N; EvenRunner(Object lck,Integer N){ this.lck=lck; this.N=N; } @Override public void run() { while(EvenOdd.i < N) { synchronized(lck) { System.out.println(" Even Thread got lock i="+EvenOdd.i); while(EvenOdd.i%2==1){ try { System.out.println(" Even Thread waiting.. i="+EvenOdd.i); lck.wait(); System.out.println(" Even Thread woken up.. i="+EvenOdd.i); }catch(InterruptedException e) { System.out.println("Interrupted thread : "+e); } } EvenOdd.i++; System.out.println(Thread.currentThread().getName()+" : i="+EvenOdd.i); System.out.println(" Even Thread Run called NotifyAll"); lck.notifyAll(); } } } }
OddRunner做同样的修改:去掉i成员变量,所有用到i的地方替换成EvenOdd.i即可。
方式二:使用可变的数值包装类
自己写一个简单的可变int包装类,这样修改的是包装类内部的值,引用不会变,volatile可以保障可见性:
class IntHolder { volatile int value; public IntHolder(int value) { this.value = value; } }
然后把EvenOdd里的静态变量改成static IntHolder i = new IntHolder(1);,Runner类里保存IntHolder的引用,操作的时候修改i.value:
// EvenRunner里的run方法核心片段 while(i.value < N) { synchronized(lck) { System.out.println(" Even Thread got lock i="+i.value); while(i.value%2==1){ try { System.out.println(" Even Thread waiting.. i="+i.value); lck.wait(); System.out.println(" Even Thread woken up.. i="+i.value); }catch(InterruptedException e) { System.out.println("Interrupted thread : "+e); } } i.value++; System.out.println(Thread.currentThread().getName()+" : i="+i.value); System.out.println(" Even Thread Run called NotifyAll"); lck.notifyAll(); } }
这种方式的好处是不用依赖静态变量,代码耦合度更低。
验证修复效果
修改后,两个线程就能正确读取到彼此更新后的i值,按照奇偶顺序打印到N了。
内容的提问来源于stack exchange,提问作者ashish2199
相关产品推荐
相关产品推荐

