设计模式之单例模式

    科技2022-09-04  114

    单例模式首先通过将类的实例化方法私有化来防止程序通过其他方式创建该类的实例,然后通过提供一个全局唯一获取该类实例的方法帮助用户获取类的实例,用户只需也只能通过调用此方法获取类的实例。

    一、懒汉模式(线程安全版)

    在获取对象实例时做了加锁操作,因此是线程安全的。

    public class LazySingleton { private static LazySingleton instance; private LazySingleton() {} // 类的具体功能待写 public static synchronized LazySingleton getInstance() { if (instance == null) { instance = new LazySingleton(); } return instance; } }

    二、饿汉模式

    懒汉模式与饿汉模式的区别在于:懒汉模式在类中定义了单例但是没有实例化,实例化的过程是在获取单例对象的方法中实现的,也就是说在第一次调用懒汉模式时,该对象一定为空,然后实例化对象并赋值,这样下次就能直接获取对象实例了;饿汉模式是在定义单例对象时就将其实例化的。也就是说,在饿汉模式下,在Class Loader完成后该类的实例便存在于JVM中了。

    public class HungrySingleton { private static HungrySingleton instance = new HungrySingleton(); private HungrySingleton() {} // 待填充具体构造方法体 public static HungrySingleton getInstance() { return instance; } }

    三、静态内部类

    因为类的静态内部类在JVM中是唯一的,这就很好地保障了单例对象的唯一性。

    为什么内部类要用static修饰呢? 这是因为在getInstance方法中我们想要通过SingletonHolder.INSTANCE获取INSTANCE实例对象就必须将INSTANCE修饰为static,这样一来,非静态内部中不能有静态成员变量,所以只能用static修饰内部类。

    为什么实例对象要用final修饰呢?可以不加吗? 因为内部类与外部类会编译成两个.class文件,内部类访问外部类的局部变量或者说外部类给内部类传形参时必须保证局部变量的值不被改变,不然就会产生不一致的问题。 在单例模型中,因为内部类没有形参也没有使用局部变量,所以也可以不加final。

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

    四、双层校验锁

    双层校验锁模式是在懒汉模式的基础做进一步优化,给静态对象的定义加上一个volatile锁来保障初始化时对象的唯一性,在获取对象时通过synchronized(Singleton.class)给单例类加锁来保障操作的唯一性。

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

    为什么instance要用volatile修饰? 因为instance = new Lock2Singleton()字段并不是一个原子性操作,实际上分为三个步骤: 1、申请一块空白内存;(空白内存) 2、调用Lock2Singleton构造函数创造对象;(写内存) 3、instance引用指向Lock2Singleton对象;(变量声明) 如果instance没有被volatile修饰: 1、线程A调用getInstance方法,instance对象没有实例化,进入synchronized块; 2、线程A执行instance = new Lock2Singleton()字段时,由于CPU对指令进行重排序,排序为上述1->3->2步骤,当线程A执行完1和3步骤后线程A被线程B预占; 3、线程B进入getInstance方法,发现instance不为null,则将未完全初始化的instance返回了。 **解决:**用volatile修饰instance后,为了保证instance的可见性,即Java线程内存模型确保所有线程看到这个变量的值是一致的,CPU就不会对instance = new Lock2Singleton()进行指令重排序。

    Processed: 0.009, SQL: 10