无法理解线程行为:读写线程注释println后无输出的问题
多线程静态变量读取的缓存一致性问题分析
问题代码
package safety.publishing; public class PublicStaticPublishing { public static void main(String[] args) { new Thread(new ReadRunnable()).start(); new Thread(new WriteRunnable()).start(); } } class A { public static int i = 0; } class ReadRunnable implements Runnable { int cache = A.i; @Override public void run() { for (;;) { if (A.i != cache) { System.out.println("cache miss " + A.i + " " + cache); cache = A.i; } else { // System.out.println("cache hit"); } } } } class WriteRunnable implements Runnable { @Override public void run() { for (int i =0; i<1000; i++) { try { Thread.sleep(10); } catch (InterruptedException e) { } A.i++; } } }
现象描述
当前代码包含一个读线程和一个写线程,出现两种截然不同的运行结果:
- 注释掉else块的
System.out.println("cache hit");时,if块的输出完全不执行,控制台持续空白; - 取消注释后,if和else块的输出都正常执行,示例输出如下:
cache hit cache hit cache hit cache hit cache hit cache hit cache hit cache hit cache hit cache hit cache miss 222 221 cache hit
原理分析
核心问题出在Java内存模型(JMM)的缓存可见性和JIT即时编译器的优化上:
无else输出时的JIT优化
读线程的循环逻辑里没有任何同步机制触发内存刷新。JIT编译器会判定A.i的值不会被其他线程修改,进而做循环不变量优化:把A.i的读取缓存到线程本地的寄存器或栈中,后续循环不再去主内存读取最新值。这就导致读线程永远看不到写线程对A.i的修改,if条件永远不成立,自然没有输出。有else输出时的内存屏障
System.out.println()方法内部包含synchronized同步块。根据JMM规则:- 退出
synchronized块时,线程会把本地缓存的变量刷新到主内存; - 进入
synchronized块时,线程会失效本地缓存,强制从主内存重新读取变量。
这相当于给读线程加了隐性的内存屏障,保证每次循环都能获取到A.i的最新值,所以能正常检测到变量变化,输出cache hit和cache miss。
- 退出
解决方案
解决这类多线程可见性问题,有两种常用方式:
- 给
A.i添加volatile关键字:class A { public static volatile int i = 0; }volatile会禁止JIT的缓存优化,保证每次读取A.i都直接从主内存获取,写入时立即刷新到主内存,强制多线程间的变量可见性。 - 使用同步锁包裹
A.i的读写操作,比如在读取和修改A.i时加synchronized块,同样能触发内存屏障,保证可见性。
内容的提问来源于stack exchange,提问作者Sandeep T
相关产品推荐
相关产品推荐

