为什么双重检查锁模式需要volatile

1.双重检查锁定 Double check locked

双重检查锁定经常出现在一些框架源码中,目的是为了延迟初始化变量。创建单例模式的创建也可以用到。

2.错误的延迟初始化例子

public class SingleMode2 {
    /**
     * 先不进行初始化
     */
    private static SingleMode2 instance = null;

    /**
     * 私有构造方法
     */
    private SingleMode2() {
    }

    /**
     * 获取实例的时候进行初始化
     *
     * @return
     */
    public static SingleMode2 getInstance() {
        if (instance != null) {
            instance = new SingleMode2();
        }
        return instance;
    }

这个例子在单线程环境下是可以正常运行的,但是在多线程环境下就有可能会创建多个实例,违反了原有单例的原本意义。我们就需要synchronized。这样该单列模式就是在多线程环境就是安全的。但是这么做就会导致每次调用该方法获取与释放锁,开销很大。

所以这个时候double check就可以让变量真正需要初始化的时候进行加锁。

所有改后的代码:

    public static SingleMode3 getInstance() {
        if (instance != null) {
            synchronized (SingleMode3.class) {
                //double check
                if (instance == null) {
                    instance = new SingleMode3();
                }
            }
        }
        return instance;
    }

double check这个方案缩小锁的范围,减少锁的开销,看起来很完美。但是这个方案 有一些问题却被忽略了。

3.new实例背后的指令

这个被忽略的问题在于 SingleMode3 instance = new SingleMode3();这行代码不是原子指令,使用的是java -c指令,可以快速查看字节码。

// 创建 SingleMode3  对象实例,分配内存
0: new           #5                  // class com/query/instance 
// 复制栈顶地址,并再将其压入栈顶
3: dup
// 调用构造器方法,初始化 SingleMode3  对象
4: invokespecial #6                  // Method "<init>":()V
// 存入局部方法变量表
7: astore_1

从字节码可以看到创建一个对象实例,可以分为三步:

1.分配对象内存 2.调用构造方法,执行初始化 3.将对象引用赋值给变量

执行情况:1–>2–>3 或1–>3–>2

虚拟机实际运行时,以上指令可能发生重排序。以上步骤2,3可能发生重排序,但是并不会重排序1的执行顺序,因为2,3指令都依托1指令的执行结果。

Java语言规定了线程执行程序时需要遵循intra-thread semantics(内部线程语义)。内部线程语义保证了重排序不会改变单线程内的执行结果。这个重排序在没有改变单线程程序的执行结果的前提下,可以提高程序的执行性能。

虽然重排序不影响单线程的执行结果,但是在多线程的环境就带来一些问题

线程1线程2
t1分配内存
t2变量赋值
t3判断对象是否为null
t4由于对象不为null,访问该对象
t5初始化对象

上面错误的双重检查锁定的示例代码中,如果线程1获取到锁进入创建对象实例,这个时候发生了指令重排序。当线程1执行到t3时刻,线程2刚好进入,由于此时对象已经不为null了,所以线程2可以访问该对象。然后对象还未初始化,所以线程2访问时将会发生异常。

4.volatile作用

正确的双重检查锁定模式需要使用volatile。volatile主要包含两个功能:

  • 保证线程可见性

    使用volatile定义的变量,将会保证对所有线程的可见性

  • 禁止指令重排序

注意,volatile禁止指令重排序在 JDK 5 之后才被修复

使用了volatile的单例模式

    /**
     * volatile 可以产生内存屏障,防止指令重排序 保证执行步骤 1.new SingleMode3()产生一个地址值  2.把地址值赋给instance 3.初始化对象
     */
    private static volatile SingleMode3 instance = null;

    /**
     * 私有构造方法
     */
    private SingleMode3() {
    }

    /**
     * 获取实例的时候进行初始化
     *
     * @return
     */
    public static SingleMode3 getInstance() {
        if (instance != null) {
            synchronized (SingleMode3.class) {
                //double check
                if (instance == null) {
                    //不是原子操作
                    instance = new SingleMode3();
                }
            }
        }
        return instance;
    }
}

5.总结

对象的创建可能发生指令的重排序,使用 volatile 可以禁止指令的重排序,保证多线程环境内的系统安全。

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
双重检查实现线程安全的单例模式是一种常用的方式。在Java多线程编程中,双重检查定(DCL)单例模式可以确保只有一个实例被创建,并且在多线程环境下保持线程安全。\[1\] 该模式的实现方式是在getInstance()方法中进行双重检查,首先检查实例是否已经被创建,如果没有,则进入同步代码块。在同步代码块中,再次检查实例是否已经被创建,如果没有,则创建一个新的实例。这样可以避免多个线程同时创建实例的问题。 然而,需要注意的是,双重检查模式在某些情况下可能会存在线程安全问题。具体来说,可能会出现代码指令重排序的情况,导致实例在多线程环境下不一致。\[2\] 为了解决这个问题,可以使用volatile关键字来修饰实例变量。volatile关键字可以确保变量的可见性和禁止指令重排序。通过在双重检查模式中使用volatile关键字修饰实例变量,可以保证在多线程环境下实例的正确创建和访问。\[1\] 总结来说,双重检查模式是一种常用的实现线程安全的单例模式的方式。通过在getInstance()方法中进行双重检查,并使用volatile关键字修饰实例变量,可以确保在多线程环境下只有一个实例被创建,并且保持线程安全。\[1\]\[2\]\[3\] #### 引用[.reference_title] - *1* *2* [java单例模式的线程安全 JAVA多线程编程中的双重检查定(DCL单例(Double Check Lock))](https://blog.csdn.net/wuyuanshun/article/details/130101511)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [【java】单例模式双重检验](https://blog.csdn.net/qq_32088869/article/details/128027274)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值