常见几种单例模式的实现方式及其优缺点

1 概念: 确保一个类在任何情况下都绝对只有一个实例(或者说只被实例化一次) ,并提供全局的访问点.

2 恶汉式单例 : 类初始化的时候就创建,即没有使用的时候就创建 .

  • 优点 : 线程是安全的,如果需要创建的单例类数量是可控的可以选择
  • 缺点: 耗费内存,如果像Spring IOC 一样需要创建的单例类的数量是不可控的 (取决于使用者).则会很耗费内存.
    常见几种单例模式的实现方式及其优缺点

3 懒汉式单例 : 仅仅当使用的时候才去创建类对象. (如果多个线程同时去获取实例,一个线程在没有创建完成的时候失去了时间片,则另一个线程也进到了if判断里,则会导致对象创建多次.)

  • 优点:节约内存
  • 缺点:线程是非安全的
    常见几种单例模式的实现方式及其优缺点

4 懒汉式升级-线程安全 (通过加上方法锁(类锁)):保证只能有一个线程进入.第一个会把他实例化后面的都是直接返回.

  • 优点:线程安全
  • 缺点:性能较低,加了锁之后同样会发生阻塞.(同一时间只能有一个线程执行方法体.其它线程阻塞,降低了整体程序的性能.)
    常见几种单例模式的实现方式及其优缺点

5 双重检查锁式单例 : 在外层多加一个判断使得程序除了第一次创建的时候是同步的,后面再去获取的就是异步的了.提升了程序的性能.但是还是在第一次创建的时候会发生同步.还是降低了性能.

  • 优点 : 提升了程序性能,通过外层多加一个判断来使得对象被实例后跳过同步机制,可以真正达到异步.
  • 缺点: 对象第一次创建时仍会发生线程阻塞 , 还有可能引发 DCL失效问题. (下文解释)
    常见几种单例模式的实现方式及其优缺点

6 DCL失效问题: 双检锁是可以保证对象只创建一次,但是由于由于编译器或CPU会发生指令重排序,而 new对象并非原子性操作,实际是分为三步如下图 (1 申请内存空间 2 初始化内存空间 3 申请的空间地址赋值给 instance 变量. )

  • 可以将通过添加 volatile 关键字声明变量 instance来解决. (volatile 可以禁止JVM对其声明的变量做指令重排序的优化)
    常见几种单例模式的实现方式及其优缺点

7 静态内部类式单例: 通过类加载本身机制来实现只实例化一次,并保证了线程安全.类加载的时候只会初始化一次.已存在的类不会再次加载。
常见几种单例模式的实现方式及其优缺点

8 如何能够破坏单例

  • 通过反射可以破坏单例
  • 通过反序列化也可以破坏单例。反序列化时候底层其实也是会通过反射创建对象.
  • 反序列化时会先通过反射创建一个对象,然后判断此被反序列化的类是否有 readResolve 方法,如果有则反射
  • 获取该方法的 Method, 然后通过刚刚反射创建的实例来调用方法即 readResolveMethod.invoke(obj, (Object[]) null); 我们可以通过这个方法返回单例对象来解决破坏,但是并不是根本解决因为反序列化会先创建一次对象.
    常见几种单例模式的实现方式及其优缺点

9 注册式单例

  • 枚举型单例:所有的实例都是 static final 的,类初始化的时候会有如下图一样静态代码块进行初始化。所有的对象都会保存到一个 $VALUES【】 的数组之中。 都可以通过名字来调用内部的 static的 valueOf 方法来从 $VALUES的数组中获取唯一一个对象实例。

    • 无法通过反射来实例化,因为Constructor 中 newInstance方法内部有判断如果是枚举则会直接抛出异常.
    • 反序列 readObject 方法中,如果是枚举类型走到 readEnum方法中也会直接调用 Enum.valueOf 方法,通过类型,和名字来获取已经实例化后的对象. 如下图反编译后的枚举类
      常见几种单例模式的实现方式及其优缺点
  • 容器式单例: 如果一个项目中很多类都需要是单例,不可能去跑到每一个类中通过上述的方法来使其全局唯一.所以则需要通过一个容器来维护已经创建好的对象,如下图
    常见几种单例模式的实现方式及其优缺点


    总结: 至于到底应该使用哪种方式则需要看具体使用的业务场景. 另外如果发现文章中哪有错误或者更好的方式实现还请指教.3 Q.