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

Java删除synchronized后指定语句不打印、循环加println可打印的原因咨询

问题底层原理

这个现象完全是Java内存模型(JMM)的可见性规则和即时编译器(JIT)优化共同作用的结果,分两种场景逐一解释:

1. 删除get/set方法的synchronized修饰符后程序卡住的原因

Java内存模型规定,所有共享变量都存储在主内存中,每个线程有自己独立的工作内存(对应CPU缓存、寄存器),线程对变量的读写都只会操作自己工作内存中的副本,默认不会主动和主内存同步。
而synchronized同步锁有明确的happen-before规则:对同一个锁的解锁操作,一定先行发生于后续对这个锁的加锁操作。这个规则的实现层面,就是解锁时会把工作内存中所有修改的共享变量刷回主内存,加锁时会清空工作内存中对应共享变量的缓存,强制从主内存拉取最新值。
你删除了两个方法的synchronized之后,started是普通的静态变量,没有任何同步机制保证它的可见性:

  • 子线程修改started = true后,修改只会存在子线程的工作内存中,不会立刻刷回主内存
  • 同时主线程的while循环里,JIT编译器会做激进优化:由于它检测不到started变量会被当前线程修改,也没有同步机制说明其他线程会修改它,所以直接把!getStarted()优化为常量true,循环变成永久死循环,自然永远不会执行后面的打印语句。

2. 循环内添加打印语句后程序能正常退出的原因

System.out是PrintStream类的实例,它的所有println方法内部都自带synchronized同步,源码示例如下:

public void println(int x) {
    synchronized (this) {
        print(x);
        newLine();
    }
}

只要你在循环里调用了println方法,就会触发两个效果:

  • 进入synchronized块时,JVM会强制清空当前线程工作内存中的共享变量缓存,下次读started时会主动从主内存拉取最新值,就能拿到子线程修改后的true
  • JIT编译器无法判断synchronized块内部会不会修改started变量,所以不会做之前的常量提升优化,每次循环都会重新读取started的值判断循环条件
    所以循环就能正常退出,打印后续内容。

正确的修复方案

要在不依赖打印语句的前提下保证程序逻辑正确,只需要做任意一种修改即可:

  • 给static boolean started添加volatile修饰符,volatile会禁止JIT对变量做指令重排和常量提升,同时要求每次读都从主内存拉取、每次写都立刻刷回主内存,直接保证可见性
  • 保留get/set方法的synchronized修饰符,依赖同步锁的happen-before规则保证可见性

内容的提问来源于stack exchange,提问作者Rafael_Kuai

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 20:24:03