为何@SpringBootApplication能让变量在多线程环境下具备可见性?
为何@SpringBootApplication能让未加volatile的变量具备可见性?
我在测试volatile的工作机制时,编写了如下代码并保留了@SpringBootApplication注解未删除。结果主线程将test设为true后,ThreadA停止运行;但按照理论,未给test添加volatile修饰时,ThreadA应持续运行。请问为何@SpringBootApplication能像volatile一样让test变量具备可见性?
@SpringBootApplication public class FeignDemoApplication { private static boolean test = false; public static void main(String[] args) throws InterruptedException { System.out.println("Thread-Main :" + test); Thread threadA = new Thread(() -> { while (!test) { } System.out.println(Thread.currentThread().getName() + " :" + test); }, "ThreadA"); threadA.start(); Thread.sleep(1000); test = true; System.out.println("main stop:" + test); } }
带@SpringBootApplication时的运行结果
Thread-Main :false main stop:true ThreadA :true Process finished with exit code 0
不带@SpringBootApplication时的运行结果
Thread-Main :false main stop:true
程序持续运行
原因分析
这本质是JVM即时编译(JIT)优化策略差异导致的:
- 无@SpringBootApplication时,程序是普通Java应用,JVM会对ThreadA中的
while (!test)循环做激进优化。因为test未被volatile修饰,JIT判定该变量在循环内不会被其他线程修改,于是将test的值缓存到线程本地寄存器中,不再每次读取主内存的最新值。主线程修改test后,ThreadA无法感知到变化,因此陷入死循环。 - 加@SpringBootApplication时,Spring Boot启动过程会触发大量类加载、初始化操作,同时创建多线程执行任务,这些行为会改变JVM的JIT编译逻辑:一方面,多线程操作可能间接触发内存屏障,促使线程本地缓存失效,ThreadA会重新读取主内存的test值;另一方面,应用复杂度提升后,JVM不会对这个简单循环进行激进的缓存优化,因此ThreadA能实时感知到test被修改为true,从而退出循环。
需要明确的是,这并不是@SpringBootApplication本身具备volatile的语义,只是Spring Boot启动流程带来的JVM行为变化,属于非确定性的巧合。绝对不能依赖这种方式保证线程安全,正确做法是使用volatile关键字、synchronized锁或者AtomicBoolean等线程安全工具类来确保变量可见性。
内容的提问来源于stack exchange,提问作者sadlarry
相关产品推荐
相关产品推荐

