zoukankan      html  css  js  c++  java
  • 双重校验锁 --使用volatile和两次判空校验

    介绍

    双重校验锁是单例模式中,饿汉式的一种实现方式。因为有两次判空校验,所以叫双重校验锁,一次是在同步代码块外,一次是在同步代码块内。

    为什么在同步代码块内还要再检验一次?

    第一个if减少性能开销,第二个if避免生成多个对象实例。

    现有三个线程A,B,C,假设线程A和线程B同时调用getSingleton()时,判断第一层if判断都为空,这时线程A先拿到锁,线程B在代码块外层等待。线程A进行第二层if判断,条件成立后new了一个新对象,创建完成,释放锁,线程B拿到锁,进行第二层if判断,singleton不为空,直接返回singleton释放锁,避免生成多个对象实例。线程线C调用getSingleton时第一层判断不成立,直接拿到singleton对象返回,避免进入锁,减少性能开销

    为什么要用volatile关键字?

    singleton = new Singleton();这行代码并不是一个原子指令,可能会在JVM中进行指令重排;

    new 实例背后的指令,我们通过使用 javap -c指令,查看字节码如下:

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

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

    (1)分配对象内存(给singleton分配内存)。

    (2)调用构造器方法,执行初始化(调用 Singleton 的构造函数来初始化成员变量)。

    (3)将对象引用赋值给变量(执行完这步 singleton 就为非 null 了)。

    在 JVM 的即时编译器中存在指令重排序的优化。指令重排并不影响单线程内的执行结果,但是在多线程内可能会影响结果。也就是说上面的2和3的顺序是不能保证的,但是并不会重排序 1 的顺序,因为 2,3 指令需要依托 1 指令执行结果。最终的执行顺序可能是 1-2-3 也可能是 1-3-2。

    1-3-2的情况

    上面多线程执行的流程中,如果线程A获取到锁进入创建对象实例,这个时候发生了指令重排序。当线程A 执行到 t3 时刻(singleton已经非null了,但是却没有初始化),此时线程 B 抢占了,由于此时singleton已经不为 Null,会直接返回 singleton对象,然后使用singleton对象,然而该对象还未初始化,就会报错。我们只需将 singleton 变量声明成 volatile 就可以禁止指令重排,避免这种现象发生。 

    参考/好文:

    菜鸟教程 – 设计模式--https://www.runoob.com/design-pattern/singleton-pattern.html

    掘金 --https://juejin.im/post/5d54c2d251882542f27bdff6

    希望本文章对您有帮助,您的转发、点赞是我的创作动力,十分感谢。更多好文推荐,请关注我的微信公众号--JustJavaIt
  • 相关阅读:
    核心编程(第七章)
    核心编程答案(第六章)
    spring aop配置切点执行了两次的原因
    spring AOP使用 xml配置
    有关于时间戳的pgsql操作
    sql 中 limit 与 limit,offset连用
    学习大数据笔记day1
    Java实现各种排序
    关于java洗牌发牌小程序
    flex.css
  • 原文地址:https://www.cnblogs.com/liaowenhui/p/12772744.html
Copyright © 2011-2022 走看看