私有构造方法 本类中提供一个静态本类对象 对外提供一个获取实例的方法
该方式的单例模式是基于懒汉式的单例模式 双重校验能保证线程安全,且避免每次获取对象都去synchronized加锁,只有单例对象为null时才去加锁创建实例对象 此外静态对象要加volatile修饰,这样能够禁止JVM的指令重排;
在14行new创建实例对象时,JVM会先为对象分配空间,然后初始化对象,再将对象引用赋给singleton 但是初始化和引用赋值由于编译器、CPU等因素会进行指令重排,使得先引用赋值再初始化 这样在多线程的时候,一条线程刚引用赋值未初始化完,另一条线程访问getInstance,由于singleton不为null就直接返回实例对象了,但由于其未初始化,其在使用时会有空指针风险
public class LazySingleton { private volatile static LazySingleton singleton; private LazySingleton() { if (LazySingleton.singleton != null) { throw new RuntimeException("单例模式只允许创建一个实例"); } } public LazySingleton getInstance() { if (singleton == null) { synchronized (LazySingleton.class) { if (singleton == null) { singleton = new LazySingleton(); //1. 分配空间 //3. 引用赋值 //2. 初始化 } } } return singleton; } }对私有构造函数,需要对其抛出异常,避免反射的时候创建实例对象
在类加载的时候,实例对象就被创建出来了;通过类加载机制来保证线程安全,(类加载过程只会进行一次)
public class HungrySingleton { private static HungrySingleton singleton = new HungrySingleton(); private HungrySingleton() { throw new RuntimeException("单例模式只允许创建一个实例"); } public static HungrySingleton getInstance() { return singleton; } }懒汉式与饿汉式,最大区别就是内存使用和线程安全方面 懒汉式在使用的时候才创建,当然,不使用就不会创建,就节省不必要的内存空间 饿汉式在类加载时就创建了,不管使不使用,当占据内存空间 此外,饿汉式本身是线程安全的,懒汉式需要做好线程安全方面的处理
此外,对于懒汉式的线程安全处理,除了双重校验锁之外,还有静态内部类和枚举单例等方式
在静态内部类InnerClassHolder中才创建InnerClassSingleton实例对象 · 静态内部类,只有在被getInstance调用的时候,才会进行类加载,类加载后便创建了实例对象,与饿汉式单例模式有异曲同工之妙
public class InnerClassSingleton { private InnerClassSingleton() { throw new RuntimeException("单例模式只允许创建一个实例"); } public static InnerClassSingleton getInstance() { return InnerClassHolder.singleton; } private static class InnerClassHolder { private static InnerClassSingleton singleton = new InnerClassSingleton(); } }枚举类的单例,本身也是线程安全的,且能够天然的避免反射创建实例,还支持反序列回同一个对象
public enum EnumSingleton { INSTANCE; public EnumSingleton getInstance(){ return INSTANCE; } }上述单例模式,除了枚举单例,其他的单例模式在实现序列化接口,在反序列时,反序列化回的对象不是同一个对象 可以通过添加Object readResolve() throws ObjectStreamException方法来解决
public Object readResolve() throws ObjectStreamException { return instance; }