简洁全面的单例模式整理

    技术2022-07-13  69

    单例实现

    保证一个类仅有一个实例,并提供一个访问它的全局访问点。

    1.静态内部类

    public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton() { } public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } }

    2.饿汉模式

    通过定义静态的成员变量,以保证单例对象可以在类初始化的过程中被实例化。 这其实是利用了ClassLoader的线程安全机制。ClassLoader的loadClass方法在加载类的时候使用了synchronized关键字。 所以, 除非被重写,这个方法默认在整个装载过程中都是线程安全的。所以在类加载过程中对象的创建也是线程安全的。

    public class Singleton { private static final Singleton instance = new Singleton(); private Singleton() { } public static Singleton getInstance() { return instance; } }

    3.懒汉模式

    双重校验锁方式实现单例。

    为什么用volatile修饰?

    因为编译器有可能进行指令重排优化,使得singleton对象再未完成初始化之前就对其进行了赋值,这样其他人拿到的对象就可能是个残缺的对象了。使用volatile的目的是避免指令重排。保证先进性初始化,然后进行赋值

    public class Singleton { private static volatile Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }

    4.枚举实现

    枚举底层依赖Enum类实现的,这个类的成员变量都是static类型的,并且在静态代码块中实例化,保证线程安全。

    枚举可以解决反序列化破坏单例的问题。

    在枚举序列化的时候,Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。 普通的Java类的反序列化过程中,会通过反射调用类的默认构造函数来初始化对象。所以,即使单例中构造函数是私有的,也会被反射给破坏掉。由于反序列化后的对象是重新new出来的,所以这就破坏了单例。 但是,枚举的反序列化并不是通过反射实现的。所以,也就不会发生由于反序列化导致的单例破坏问题。

    public enum Singleton { INSTANCE; public void whateverMethod() { } }

    5.CAS实现

    private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<Singleton>(); private Singleton() {} public static Singleton getInstance() { for (;;) { Singleton singleton = INSTANCE.get(); if (null != singleton) { return singleton; } singleton = new Singleton(); if (INSTANCE.compareAndSet(null, singleton)) { return singleton; } } }

    单例破坏

    反射破坏

    private void distoryByReflect() { Singleton singleton = Singleton.getSingleton(); try { Class<Singleton> singleClass = (Class<Singleton>)Class.forName("com.esparks.pandora.learning.designmode.Singleton"); Constructor<Singleton> constructor = singleClass.getDeclaredConstructor(null); constructor.setAccessible(true); Singleton singletonByReflect = constructor.newInstance(); System.out.println("singleton : " + singleton); System.out.println("singletonByReflect : " + singletonByReflect); System.out.println("singleton == singletonByReflect : " + (singleton == singletonByReflect)); } catch (Exception e) { e.printStackTrace(); } }

    解决方式:在Singleton的构造函数中增加如下代码

    private Singleton() { if (singleton != null) { throw new RuntimeException("Singleton constructor is called... "); } }

    序列化破坏

    通过先序列化再反序列化的方式,可获取到一个新的单例对象,这就破坏了单例。

    private void distoryBySerialize() { Singleton singleton = Singleton.getSingleton(); //Write Obj to file ObjectOutputStream oos = null; try { oos = new ObjectOutputStream(new FileOutputStream("tempFile")); oos.writeObject(singleton); //Read Obj from file File file = new File("tempFile"); ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file)); Singleton singletonBySerialize = (Singleton)ois.readObject(); //判断是否是同一个对象 System.out.println("singleton : " + singleton); System.out.println("singletonBySerialize : " + singletonBySerialize); System.out.println("singleton == singletonBySerialize : " + (singleton == singletonBySerialize)); } catch (Exception e) { e.printStackTrace(); } }

    解决方式:在Singleton中增加readResolve方法,并在该方法中指定要返回的对象的生成策略几可以了。即序列化在Singleton类中增加以下代码即可:

    private Object readResolve() { return getSingleton(); }
    Processed: 0.038, SQL: 9