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
相关产品推荐
相关产品推荐

