zoukankan      html  css  js  c++  java
  • 简单的单例模式其实也不简单

    单例模式可以说只要是一个合格的开发都会写,但是如果要深究,小小的单例模式可以牵扯到很多东西,比如 多线程是否安全,是否懒加载,性能等等。还有你知道几种单例模式的写法呢?如何防止反射破坏单例模式?今天,我就花一章内容来说说单例模式。

    关于单例模式的概念,在这里就不在阐述了,相信每个小伙伴都了如指掌。

    我们直接进入正题:

    饿汉式


     

    饿汉式是最简单的单例模式的写法,保证了线程的安全,在很长的时间里,我都是饿汉模式来完成单例的,因为够简单,后来才知道饿汉式会有一点小问题,看下面的代码:


     

    在Hungry类中,我定义了四个byte数组,当代码一运行,这四个数组就被初始化,并且放入内存了,如果长时间没有用到getInstance方法,不需要Hungry类的对象,这不是一种浪费吗?我希望的是 只有用到了 getInstance方法,才会去初始化单例类,才会加载单例类中的数据。所以就有了 第二种单例模式:懒汉式。

    懒汉式(DCL)


     

    DCL懒汉式的单例,保证了线程的安全性,又符合了懒加载,只有在用到的时候,才会去初始化,调用效率也比较高,但是这种写法在极端情况还是可能会有一定的问题。因为


     

    不是原子性操作,至少会经过三个步骤:

    分配内存

    执行构造方法

    指向地址

    由于指令重排,导致A线程执行 lazyMan = new LazyMan();的时候,可能先执行了第三步(还没执行第二步),此时线程B又进来了,发现lazyMan已经不为空了,直接返回了lazyMan,并且后面使用了返回的lazyMan,由于线程A还没有执行第二步,导致此时lazyMan还不完整,可能会有一些意想不到的错误,所以就有了下面一种单例模式。

    懒汉式(Volatile)

    这种单例模式只是在上面DCL单例模式增加一个volatile关键字来避免指令重排:


     

    持有者


     

    这种方式是第一种饿汉式的改进版本,同样也是在类中定义static变量的对象,并且直接初始化,不过是移到了静态内部类中,十分巧妙。既保证了线程的安全性,同时又满足了懒加载。

    万恶的反射

    万恶的反射登场了,反射是一个比较霸道的东西,无视private修饰的构造方法,可以直接在外面newInstance,破坏我们辛辛苦苦写的单例模式。


     

    我们分别打印出lazyMan1,lazyMan2的hashcode,lazyMan1是否相等lazyMan2,结果显而易见:


     

    那么,怎么解决这种问题呢?


     

    在私有的构造函数中做一个判断,如果lazyMan不为空,说明lazyMan已经被创建过了,如果正常调用getInstance方法,是不会出现这种事情的,所以直接抛出异常:


     

    但是这种写法还是有问题:

    上面我们是先正常的调用了getInstance方法,创建了LazyMan对象,所以第二次用反射创建对象,私有构造函数里面的判断起作用了,反射破坏单例模式失败。但是如果破坏者干脆不先调用getInstance方法,一上来就直接用反射创建对象,我们的判断就不生效了:


     

    那么如何防止这种反射破坏呢?


     

    在这里,我定义了一个boolean变量flag,初始值是false,私有构造函数里面做了一个判断,如果flag=false,就把flag改为true,但是如果flag等于true,就说明有问题了,因为正常的调用是不会第二次跑到私有构造方法的,所以抛出异常:


     

    看起来很美好,但是还是不能完全防止反射破坏单例模式,因为可以利用反射修改flag的值。

    看起来并没有一个很好的方案去避免反射破坏单例模式,所以轮到我们的枚举登场了。

    枚举


     

    枚举是目前最推荐的单例模式的写法,因为足够简单,不需要开发自己保证线程的安全,同时又可以有效的防止反射破坏我们的单例模式,我们可以看下newInstance的源码:


     

    重点就是红框中圈出来的部分,如果枚举去newInstance就直接抛出异常了。

    扩展阅读

    线程池的好处,详解,单例(绝对好记)

    java中的设计模式之单例模式、工厂模式

    面试中单例模式有几种写法

    Hi,我们再来聊一聊Java的单例吧

    Web登录其实没那么简单

    单点登录原理与简单实现

    来源:https://www.cnblogs.com/CodeBear/p/10212529.html

  • 相关阅读:
    图像检索(image retrieval)- 11
    图像检索(image retrieval)- 10相关
    Mock.js简易教程,脱离后端独立开发,实现增删改查功能
    Azure Monitor (3) 对虚拟机磁盘设置自定义监控
    Azure Monitor (1) 概述
    Azure SQL Managed Instance (2) 备份SQL MI
    Azure Virtual Network (17) Private Link演示
    Azure Virtual Network (16) Private Link
    Azure Virtual Network (15) Service Endpoint演示
    Azure Virtual Network (14) Service Endpoint服务终结点
  • 原文地址:https://www.cnblogs.com/javafirst0/p/10767182.html
Copyright © 2011-2022 走看看