为什么懒汉式单例模式要加volatile修饰符

发布时间 2023-10-04 11:15:28作者: 斌哥的小弟
public class LazySingleton {
    private LazySingleton() {
    }
 
    private volatile static LazySingleton instance;  
   
    public synchronized static LazySingleton getInstance() {    

if (instance == null) { instance = new LazySingleton(); } return instance; } }

 

 

在多线程环境下,为了提高程序的性能,编译器和处理器可能会对指令进行重排。指令重排是将原本按照代码顺序执行的指令,重新排序成其他的顺序来提高并行度或者优化性能。

然而,当涉及到多线程的情况时,指令重排可能会导致意想不到的结果。特别是对于读写 volatile 变量的操作,指令重排可能会破坏程序的内存可见性,即其他线程无法看到最新的值。

这是因为编译器和处理器可能会将对 volatile 变量的读写操作重新排序到其他指令的前面或后面。为了解决这个问题,Java 内存模型(Java Memory Model,JMM)规定了 volatile 变量的特殊语义。

volatile 变量不仅保证了可见性,还保证了禁止指令重排。具体来说,在写入 volatile 变量之后,JMM 会插入内存屏障(memory barrier),将其之前的指令刷新到主内存中,并禁止之后的指令被

重排到内存屏障之前。同样,在读取 volatile 变量之前,JMM 会插入内存屏障,确保之前的指令获取到最新的值。

 

对于这个案例而言:

instance = new TestInstance(); 可以分解为3行伪代码:

a. memory = allocate() // 堆区分配内存
b. ctorInstanc(memory) // 栈区初始化对象
c. singleton = memory // singleton对象指向刚分配的内存地址

上面的代码在编译运行时,可能会出现重排序从a-b-c排序为a-c-b。

a. memory = allocate() // 堆区分配内存
c. singleton = memory // singleton对象指向刚分配的内存地址
b. ctorInstanc(memory) // 栈区初始化对象

 

这就运行错误了,因此需要volatile,简单来说就是防止重新排序