Java volatile关键字疑问:未声明volatile的代码为何会终止?
为什么未加volatile的共享变量也能被线程感知到修改?
这个问题其实挺常见的——你踩了「测试可见性问题时不小心引入了同步操作」的坑,我来给你拆解清楚:
核心原因:你的循环里有触发内存同步的操作
你预期线程会一直读取自己工作内存里的counter副本,永远看不到其他线程的修改,但实际程序终止了,是因为第二个线程的循环里有两个关键操作:System.out.println()和Thread.sleep(),它们会强制线程和主内存同步数据:
System.out.println()内部带锁:查看JDK源码会发现,PrintStream的println方法内部调用了加synchronized锁的print方法。当线程进入或退出同步代码块/方法时,JVM会强制线程把工作内存的变量写回主内存,同时从主内存刷新最新的变量值。Thread.sleep()的隐性同步:虽然sleep不会释放锁,但线程在进入睡眠状态和唤醒时,也会触发内存屏障,迫使线程同步主内存的变量状态。
所以你的第二个线程每次执行完println和sleep,都会去主内存重新读取counter的最新值——自然就能看到第一个线程累加后的结果,当counter超过10000时,循环就终止了。
真正能复现“永不终止”的场景
如果你把第二个线程里的println和sleep都去掉,改成一个空循环,就能看到你预期的结果了:
public class VolatileTest { public static void main(String[] args) throws InterruptedException { var holder = new Holder(); new Thread(() -> { try { Thread.sleep(500); } catch (InterruptedException e) { } for(int i = 0; i<100000; i++) { holder.counter++; } }).start(); var t = new Thread(() -> { // 空循环,没有任何触发内存同步的操作 while(holder.counter < 10000) { // 什么都不做 } System.out.println("循环终止了"); }); t.start(); t.join(); } static class Holder { int counter = 0; // 加上volatile后,空循环也会终止:volatile int counter = 0; } }
这时候JVM会做循环优化,把counter的值缓存到寄存器或者线程的工作内存里,不再去主内存读取最新值——即使第一个线程已经把counter加到100000,第二个线程也会一直读旧值,循环永远不会结束。
补充:可见性问题不是绝对的
Java内存模型只是允许线程缓存变量,不是强制要求。在某些情况下,即使没有同步操作,线程也可能偶然刷新主内存的值,但这种情况是不可靠的。只有用volatile、synchronized或者其他并发工具类,才能保证可见性的一致性。
内容的提问来源于stack exchange,提问作者Maciuch Zet
相关产品推荐
相关产品推荐

