2019-04-02-singleton
👗

2019-04-02-singleton

/** * 单例模式,懒汉式,线程不安全 */ public class Singleton { private static Singleton uniqueInstance; private Singleton() {} public static Singleton getInstance() { if (uniqueInstance == null) { uniqueInstance = new Singleton(); } return uniqueInstance; } }
/** * 单例模式,饿汉式,线程安全 */ public class Singleton2 { private static Singleton2 instance = new Singleton2(); private Singleton2() {} public static Singleton2 getInstance() { if (instance == null) { instance = new Singleton2(); } return instance; } }
/** * 单例模式,饿汉式,线程安全,多线程环境下效率不高 */ public class Singleton3 { private static Singleton3 instance = null; private Singleton3() {} public static synchronized Singleton3 getInstance() { if (instance == null) { instance = new Singleton3(); } return instance; } }
/** * 单例模式,饿汉式,由 static 块保证线程安全 */ public class Singleton4 { private static Singleton4 instance; static { instance = new Singleton4(); } private Singleton4() {} public static Singleton4 getInstance() { return instance; } }
/** * 单例模式,懒汉式,使用静态内部类,线程安全【推荐】 */ public class Singleton5 { private class SingletonHolder { private static final Singleton5 INSTANCE = new Singleton5(); } private Singleton5() {} public static Singleton5 getInstance() { return SingletonHolder.INSTANCE; } }
/** * 使用枚举方式,线程安全【推荐】 * * 枚举自己处理序列化 */ public enum Singleton6 { //调用 Singleton6.INSTANCE.whateverMethod() INSTANCE public void whateverMethod() { } }
/** * 使用双重校验锁,线程安全【推荐】 */ public class Singleton7 { private volatile static Singleton7 instance = null; private Singleton7() {} public static Singleton7 getInstance() { if (instance == null) { synchronized (Singleton7.class) { if (instance == null) { instance = new Singleton7(); } } } return instance; } }

小结

推荐:静态内部类、枚举、双重校验锁
最好的单例模式:枚举

枚举

使用枚举实现单例模式是最好的方法,因为
  1. 写法简单
    1. public enum Singleton6 { //调用 Singleton6.INSTANCE.whateverMethod() INSTANCE public void whateverMethod() { } }
  1. 枚举实例创建是线程安全的
    1. 当一个Java类第一次被真正使用到的时候,静态资源被初始化。Java类的加载和初始化过程都是线程安全的。enum类型会被编译器编译成class T extends Enum的类。所以,创建一个enum类型是线程安全的。
  1. 枚举自己处理序列化
    1. 普通的Java类的反序列化过程中,会通过反射调用类的默认构造函数来初始化对象。所以,即使单例中构造函数是私有的,也会被反射给破坏掉。由于反序列化后的对象是重新new出来的,所以这就破坏了单例。但是,枚举的反序列化并不是通过反射实现的。所以,也就不会发生由于反序列化导致的单例破坏问题。
      关于枚举类的序列化和反序列化:在序列化的时候Java仅仅是将枚举对象的属性输出到结果中,反序列化的时候则是通过java.lang.EnumvalueOf方法来根据名字查找枚举对象。同时,编译器不允许对这种序列化机制进行定制,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。

双重校验锁

volatile 关键字的含义: - 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,新值对其他线程是立即可见的 - 禁止进行指令重排序
volatile 关键字在本实现的主要作用:禁止进行指令重排序。因为类初始化分两步: volatile 的不足:无法保证原子性,所以要结合synchronized实现
volatile 的使用条件: - 对变量的写操作不依赖于当前值 - 该变量没有包含在具有其他变量的不变式中 (a <= b)

静态内部类

优点:延迟加载、线程安全、访问成本低
延迟加载原理:类级内部类只有在第一次被使用的时候才被会装载。 当getInstance方法第一次被调用的时候,它第一次读取SingletonHolder.instance,内部类SingletonHolder类得到初始化;而这个类在装载并被初始化的时候,会初始化它的静态域,从而创建Singleton的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次。
线程安全:由虚拟机来保证它的线程安全性。
访问成本低:getInstance方法并没有被同步,并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本。