美文网首页
单例模式 -- 懒汉式

单例模式 -- 懒汉式

作者: wbpailxt | 来源:发表于2019-11-24 18:20 被阅读0次
    图片.png

    这样看起来似乎实现了单例模式,但是它目前是线程不安全的。


    图片.png

    解决办法:


    图片.png
    加在静态方法上相当于锁是这个类的class文件,如果加在普通成员方法上,相当于锁的是堆内存中的生成对象
    图片.png
    但是同步锁会比较消耗资源,这里边有加锁和解锁的开销,而且synchronized修饰static方法时,锁的是这个class,锁的范围较大,对性能比较有影响。

    而且现在还存在一个问题,就是当第一个实例创建出来后,此时应该是不会加锁,判断非空直接返回实例。但是它依旧会先给类加上锁,返回实例出去再解锁。这过程中,在已经有实例的前提情况下依旧加锁就是白白浪费计算机资源了。

    DoubleCheck兼顾了性能和线程安全

    为了解决已经有了实例还会加锁这个问题,先判断是否已经有实例再决定是否加锁,已经有实例就不加锁,直接返回实例出去了。

    图片.png
    但这里面依旧有隐患
    lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();这一行看似只有一行,但实际上经历了3个步骤。
    1.分配内存给这个对象
    2.初始化对象
    3.设置lazyDoubleCheckSingleton 指向刚分配的内存地址
    这3个步骤的第2步骤,第3步骤有可能会被颠倒。
    对于单线程,第2步骤,第3步骤被颠倒不会影响执行结果。但对于多线程就不一定了。
    图片.png
    上述情况中,线程1比线程0更早地访问对象,线程1访问到的对象是在线程0中还没有初始化完成的一个对象,系统就会报异常。
    解决办法:不允许2和3重排序或者允许重排序,但不允许别的线程看见重排序
    在变量前加volatile后该变量在new对象时重排序就会被禁止,线程就安全了
    此时步骤2和步骤3严格按先后顺序执行。
    允许重排序,但不允许别的线程看见重排序
    通过静态内部类(本质是基于类的初始化)来解决。
    jvm在类的初始化阶段,也就是class文件被加载后并且被线程使用之前都是类的初始化阶段,在这个阶段会执行类的初始化,在执行类的初始化期间呢,jvm回去获取一个锁,这个锁可以同步多个线程对一个类的初始化,也就是蓝色的部分,基于这个特性,我们可以实现基于静态内部类的并且线程安全的的延迟初始化方案。
    一个类(泛指,接口也算)会被立刻初始化的五种情况
    1、A类型实例被实现
    2、A类型的静态方法被调用
    3、A类中的静态成员被赋值
    4、A类中的一个静态成员并且这个成员不是一个常量成员。
    5、A类是一个顶级类
    图片.png
    图片.png
    静态内部类这种方式的核心在于InnerClass这个类的Class对象初始化锁看哪个线程拿到。

    拓展:
    1、编译器优化的重排序,在不改变单线程语义的情况下重新安排语句的执行顺序
    2、指指令级并行重排序,处理器的指令级并行技术将多条指令重叠执行,如果不存在数据的依赖性将会改变语句对应机器指令的执行顺序
    出自:https://blog.csdn.net/qq_41174684/article/details/90631746

    图片.png

    相关文章

      网友评论

          本文标题:单例模式 -- 懒汉式

          本文链接:https://www.haomeiwen.com/subject/pwfiictx.html