设计模式06 - 设计模式 - 单例设计模式(高频-创建型)
一、定义
单例设计模式(Singleton Design Pattern)理解起来非常简单。一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。因此,不允许new对象出来,构造函数肯定私有。
只确保一个类再任何情况下都绝对只有一个实例,并提供一个全局访问点
二、实现方式
2.1 饿汉式 - 安全
饿汉式:项目启动,就初始化,不是使用时才初始化,非常安全
1 public final class SingleObject { 2 // 在定义实例对象的时候直接初始化 3 private static SingleObject instance = new SingleObject(); 4 5 // 私有构造函数,不允许外部new 6 private SingleObject() {} 7 8 public static SingleObject getInstance() { 9 return instance; 10 } 11 }
分析:
SingleObject类是使用final修饰的,不允许被继承。在饿汉式的单例模式下,SingleObject类对象instance对象在类加载的初始化阶段就会直接完成创建,因此该方法在多线程的情况下也能保证同步,因为不可能被实例化两次,不存在线程不安全的情况。 适用场景:如果一个类的成员属性比较少,且占用的内存资源不多,饿汉式的性能是比较高的;
2.2 懒汉式 - 不安全
1 public final class SingleObject { 2 //懒加载 3 private static SingleObject instance = null; 4 5 private SingleObject() {} 6 //使用时在加载 7 public static SingleObject getInstance() { 8 if (instance == null) { 9 instance = new SingleObject(); 10 } 11 return instance; 12 } 13 }
安全性能分析:
如果两个线程A和B,A调用getInstance(),执行instance = new Singletion()需要一定时间;在这段时间中,B也调用了getInstance(),也要去执行instance = new SingleObject(),这样instance就被实例化了两次,线程不同步了。
有人说:可以对整个方法加synchronized解决,但是性能太低,同时只可以有一个线程使用;不行;
2.3 懒汉升级 Double-Check - 安全
1 public final class SingleObject { 2 3 private byte[] data = new byte[1024]; 4 5 // 加volatile关键字保证有序性,防止指令重排序, 6 private volatile static SingleObject instance = null; 7 8 private SingleObject() { 9 //有可能做很多事情,比如做socket连接,可能就指令重排了 10 } 11 public static SingleObject getInstance() { 12 13 if (instance == null) { //判断是否已经实例化,如果实例化,就直接获取了 14 synchronized (SingleObject.class) { 15 if (instance == null) { //有可能有A、B两个线程同时经过了第一个if,假设A抢到了资源,那么防止B在进入的时候再次创建 16 //防止指令重拍 17 instance = new SingleObject(); 18 } 19 } 20 } 21 return instance; 22 } 23 }
分析:
如果不加volatile,假设线程A第一个调用getInstance()方法,此时instance == null,因此A获取到同步资源,对instance进行加载。由于指令会被重排序,在SingleObject的构造函数中,有可能instance最先被实例化,而socket连接后被实例化。在socket被实例化的过程中,又有一个线程B调用了getInstance()方法,它发现instance != null,则获取到了instance,然后又去使用socket,就会产生空指针异常。
这种方式安全,但是不常用;
2.4 Holder方式 - 内部类方式 - 推荐
1 public final class SingleObject { 2 3 private byte[] data = new byte[1024]; 4 5 private SingleObject() {} 6 7 // 在静态内部类中持有SingleObject的实例,并且可被直接初始化 8 private static class Holder { 9 private static SingleObject instance = new SingleObject(); 10 } 11 12 // 调用getInstance()方法,实际上是获得Holder的instance静态属性 13 public static SingleObject getInstance() { 14 return Holder.instance; 15 } 16 }
Holder方式完全借助了类加载的特性。在SingleObject类中并没有instance的静态成员变量,只有在其静态内部类Holder中有。因此,在SingleObject类初始化时,并不会创建Singleton的实例。只有在getInstance()方法第一次被调用时,Singleton的实例instance才会被创建,由此实现了懒加载。另外,这个创建过程是在Java编译时期收集至< clinit>()方法(JVM的一个内部方法,在类加载的初始化阶段起作用)中的。这个方法是同步方法,保证内存的可见性、JVM指令的原子性和有序性。 因此Holder方式可以保证线程安全、高性能和懒加载,它也是单例模式中最好的设计之一,使用非常广泛。
2.5 枚举方式 - 推荐
在很多优秀开源代码中经常可以看到枚举方式的例子,因为优雅
1 public enum SingleObject { 2 INSTANCE; 3 4 private static String data = ""; 5 6 SingleObject() { 7 System.out.println("INSTANCE will br initialized immediately"); 8 } 9 10 public static void method() { 11 // 调用该方法将会主动使用SingleObject, INSTANCE将会被初始化,绕过了getInstance()去调用,因此不能懒加载,我们希望调用getInstance()方法获取单例; 12 data = "123"; 13 } 14 //调用该方法得到实例 15 public static SingleObject getInstance() { 16 return INSTANCE; 17 } 18 19 }
枚举方式不能被继承,只能实例化一次,同样也是线程安全,且高效的。但是枚举方式不能懒加载,第一次调用Singleton枚举时,instance就会被实例化。
2.6 最强王者 枚举 + holder - 推荐
1 public class SingleObject { 2 3 private byte[] data = new byte[1024]; 4 5 private SingleObject() {} 6 7 private enum EnumHolder { 8 INSTANCE; 9 10 private SingleObject instance; 11 12 EnumHolder() { 13 this.instance = new SingleObject(); 14 } 15 16 private SingleObject getSingleObject() { 17 return instance; 18 } 19 } 20 21 public static SingleObject getInstance() { 22 return EnumHolder.INSTANCE.getSingleObject(); 23 } 24 }
各个方式总结
线程安全 | 高性能 | 高性能 | 懒加载 | 补充 |
---|---|---|---|---|
饿汉式 | √ | √ | × | |
懒汉式 | × | √ | √ | |
Double-Check+Volatile | √ | √ | √ | 加Volatile防止控制在异常 |
Holder方式 | √ | √ | √ | |
枚举方式 | √ | √ | × | 可以添加Holder方式实现懒加载 |
枚举方式+Holder方式 | √ | √ | √ | 最强王者 |
三、使用场景 - 待补充