指令重排与更安全地单例模式创建,加深理解

多线程下『指令重排』会影响正确性,例如著名的 double-checkedlocking 模式实现单例

指令重排就是jvm会根据代码的复杂性,在"认为"不会影响正确加的情况下,对代码的执行的顺序进行调换。

scss 复制代码
   public static Singleton getInstance( )  {
       // 实例没创建,才会进入内部的 synchronized代码块
        if(INSTANCE == null) {
            synchronized (Singleton.class) {
                // 也许有其它线程已经创建实例,所以再判断一次
                if(INSTANCE == null) {
                    INSTANCE = new Singleton();
                }
            }
        }
        return INSTANCE;
    }

线程1来了,发现对象没创建,进入同步代码块,锁住类对象,执行下面的代码,如果同一时间,第二线程也来操作,就会等待第一个线程执行结束,第一线程同步代码块锁解开了,第二线程进入了,如果第二个线程进来以后,如果没有内层判断,就会又创建一个对象,等第二个线程进来以后还要加一个if判断,如果为空了再创建,创建好了就直接返回instance就好了。

以上的实现特点是:

  • 懒惰实例化
  • 首次使用 getInstance() 才使用 synchronized 加锁,后续使用时无需加锁

但在多线程环境下,上面的代码是有问题的,没有考虑指令重排的问题, 重点代码在:INSTANCE = new Singleton() 对应的字节码为:

yaml 复制代码
0: new #2 // class cn/itcast/jvm/t4/Singleton 执行结果会把对象引用放入操作数栈
3: dup   //操作数栈把对象复制了一份。第一个交给了invokespecial调用构造方法,第二个交给了putstatic
4: invokespecial #3 // Method "<init>":()V
7: putstatic #4 // Field
INSTANCE:Lcn/itcast/jvm/t4/Singleton;

其中 4 7 两步的顺序不是固定的,也许 jvm 会优化为:先将引用地址赋值给 INSTANCE 变量后,再执行构造方法,如果两个线程 t1,t2 按如下时间序列执行:

java 复制代码
时间1 t1 线程执行到 INSTANCE = new Singleton();
时间2 t1 线程分配空间,为Singleton对象生成了引用地址(0 处)
时间3 t1 线程将引用地址赋值给 INSTANCE,这时 INSTANCE != null(7 处)
时间4 t2 线程进入getInstance() 方法,发现 INSTANCE != null(synchronized块外),直接
返回 INSTANCE
时间5 t1 线程执行Singleton的构造方法(4 处)

这时 t1 还未完全将构造方法执行完毕,如果在构造方法中要执行很多初始化操作,那么 t2 拿到的是将是一个未初始化完毕的单例

对 INSTANCE 使用 volatile 修饰即可,可以禁用指令重排,但要注意在 JDK 5 以上的版本的 volatile 才会真正有效。

单例懒惰创建

java 复制代码
 
//禁用指令重排
private static volatile Singleton INSTANCE = null;
​
public static Singleton getInstance( )  {
        if(INSTANCE == null) {
            synchronized (Singleton.class) {
                if(INSTANCE == null) {
                    INSTANCE = new Singleton();
                }
            }
        }
        return INSTANCE;
    }
相关推荐
Amos_Web7 分钟前
通过 Rspack 源码学 Rust(一):`mod`、`pub` 与 `use crate`/`use super`
后端
张彦峰ZYF19 分钟前
拆得开不等于该拆开:微服务与模块化单体的边界判据、规模阈值、团队适配与一致性代价
后端·微服务·边界是否稳定·扩展天花板是否到顶·有无小组能独立值班·独立部署的自由会否被用上·这一刀是否切断不可降级的不变量
小淮AI28 分钟前
面试前 48 小时,怎样练表达和复盘:一份个人准备记录
面试·职场和发展
是江迪呀31 分钟前
介绍一个开源项目 —— 烂笔头
前端·后端
考研保研资料分享40 分钟前
双非转专业保研海军军医大学心理学:四校考核、学年论文与择校复盘
面试
captain37642 分钟前
Spring Web MVC(2)
java·前端·spring boot·后端·spring·mvc
紧跟先前的步伐43 分钟前
Go AI Agent 实战 17|模型适配层设计:让模型可替换
开发语言·人工智能·后端·ai·golang
IT_陈寒1 小时前
一文搞懂Java的核心概念
前端·人工智能·后端
铁皮饭盒1 小时前
workbuddy和codebuddy免费送1000积分, 10秒钟搞定
前端·后端
计算机就业还在扩大1 小时前
第2期-cmp.Ordered+类型推断+刻意未支持的特性
后端·面试·golang