设计模式(1)--单例模式概念要点及例子说明

    科技2022-07-11  108

    0. 要点:

     私有构造方法  本类中提供一个静态本类对象  对外提供一个获取实例的方法

    1. 双重校验的单例模式:

     该方式的单例模式是基于懒汉式的单例模式  双重校验能保证线程安全,且避免每次获取对象都去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; } }

     对私有构造函数,需要对其抛出异常,避免反射的时候创建实例对象

    1. 饿汉式:

     在类加载的时候,实例对象就被创建出来了;通过类加载机制来保证线程安全,(类加载过程只会进行一次)

    public class HungrySingleton { private static HungrySingleton singleton = new HungrySingleton(); private HungrySingleton() { throw new RuntimeException("单例模式只允许创建一个实例"); } public static HungrySingleton getInstance() { return singleton; } }

     懒汉式与饿汉式,最大区别就是内存使用和线程安全方面   懒汉式在使用的时候才创建,当然,不使用就不会创建,就节省不必要的内存空间   饿汉式在类加载时就创建了,不管使不使用,当占据内存空间  此外,饿汉式本身是线程安全的,懒汉式需要做好线程安全方面的处理

     此外,对于懒汉式的线程安全处理,除了双重校验锁之外,还有静态内部类和枚举单例等方式

    2. 静态内部类单例模式:

     在静态内部类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(); } }

    3. 枚举单例模式:

     枚举类的单例,本身也是线程安全的,且能够天然的避免反射创建实例,还支持反序列回同一个对象

    public enum EnumSingleton { INSTANCE; public EnumSingleton getInstance(){ return INSTANCE; } }

     上述单例模式,除了枚举单例,其他的单例模式在实现序列化接口,在反序列时,反序列化回的对象不是同一个对象  可以通过添加Object readResolve() throws ObjectStreamException方法来解决

    public Object readResolve() throws ObjectStreamException { return instance; }
    Processed: 0.009, SQL: 8