• 双重校验锁 --使用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
  • 相关阅读:
    微软Office团队做产品的十大经验教训 沧海
    Definitio of Business Intelligence 沧海
    爱购家园,轻松工作,轻松生活 沧海
    SAAS概述 沧海
    一定要做自己最内行的东西,一定要在自己本身的职位上来提升自己 沧海
    Android应用的构成 沧海
    管理的最高境界,就是激发每个员工的潜力。让每个员工都觉得自己的工作是一份事业。 沧海
    The life cycle of a typical project 一个典型的项目生命周期 沧海
    印度英语的特点归纳 沧海
    3G时代的移动业务赢利模式分析 沧海
  • 原文地址:https://www.cnblogs.com/liaowenhui/p/12772744.html
Copyright © 2020-2023  润新知