You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 18:47:37