所谓单例模式就是保证一个类只有一个实例,并提供一个访问它的方法。
我们常常在整个系统中只需要一个对象实例的情况下使用单例模式。
在SpringBoot中,通过@Controller、@Service注解注入的对象都是默认单例的。
单例模式除了可以保证唯一的实例外,还可以严格的控制用户怎么访问它、何时访问它。简单来说就是对唯一实例的受控访问。
而根据实例的生成时间可以将单例模式分为饿汉式和懒汉式。
所谓懒汉式,就是延迟加载实例。
接下来我们先用一段的代码向大家展示什么是懒汉式。
public class SingletonLazy {
// 默认为空,等到真正要获取实例的时候再生成
private static SingletonLazy instance;
// 构造方法私有,禁止使用构造方法生成实例对象
private SingletonLazy(){};
// 通过getInstance()获取唯一实例
public SingletonLazy getInstance(){
if(instance == null)
instance = new SingletonLazy();
return instance;
}
}
这样的代码在单线程下当然是可通的,但是如果是多线程的情况下,可能会生成多个实例。
所以通常情况下我们会对getInstance()方法加锁,避免生成多个实例对象。
public class SingletonLazy {
private static SingletonLazy instance;
// 创建一个辅助资源类对象
private static Object syncRoot = new Object();
private SingletonLazy(){};
public SingletonLazy getInstance(){
// 对生成实例对象的代码加锁
synchronized (syncRoot) {
if (instance == null)
instance = new SingletonLazy();
}
return instance;
}
}
但是仅仅这样做,还不够。
在多线程情况下,如果某一个线程进入了synchronized代码块,它倒是爽了,但是其他线程都要在这里苦苦等待,执行效率比较低。
所以在此我们引入了双重锁定(Double-Check Lock)。
public class SingletonLazy {
// 这里注意,使用volatile关键字是为了避免指令重排
// 比如说instance = new SingletonLazy()这一行代码,实际上是分为三个步骤执行的
// (1)为instance分配内存空间
// (2)初始化instance
// (3)将instance指向分配的内存地址
// 如果指令重排以后,可能发生某个线程调用getInstance()方法引用以后未初始化的instance对象
private volatile static SingletonLazy instance;
private SingletonLazy(){};
public SingletonLazy getInstance(){
// 第一道锁
if(intance == null){
// 第二道锁
synchronized (SingletonLazy.class) {
if (instance == null)
instance = new SingletonLazy();
}
}
return instance;
}
}
双重锁定以后,线程判断instance不为null以后,就会直接返回实例,不会再苦苦等待。
当然你也可能有点懵,为什么要加两个判断instance是否为null。
之所以这样做,是因为,如果一开始instance没有被实例化,然后两个线程执行该方法,他们都通过了第一道锁,如果没有第二个判断,那么两个线程最后会依次生成实例对象,这样就破化了单例模式。所以第二道锁中还需要在判断一次instance是否为空。
所谓饿汉式就是静态初始化,就是说当类被加载的时候就将自己实例化。
public class SingletonHungry {
// 当类加载的时候就直接实例化对象
private static SingletonHungry instance = new SingletonHungry();
private SingletonHungry(){}
public static SingletonHungry getInstance(){
return instance;
}
}
由于饿汉式,即静态初始化的方式,它是类一加载就实例化的 对象,所以要提前占用系统资源。然而懒汉式,又会面临着多线程访问的安全性问题,需要做双重锁定这样的处理才可以保证安全。所以到底使用哪一种方式,取决于实际的需求。
通常情况下: