首先咱来聊聊什么是单例模式?有几种?
单例模式,即 确保一个类在多线程环境下只有一个实例,并提供一个全局访问点来访问这个唯一实例。
单例模式有 饿汉式单例模式、懒汉式单例模式、双检锁单例模式、DCL双端锁 四种。
1、饿汉式
线程安全,一开始就初始化
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton () {}
public static Singleton getInstance() {
return instance;
}
}
2、懒汉式
非线程安全,延迟初始化。
public class Singleton {
private static Singleton instance;
private Singleton () {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
3、双检锁
线程安全,延迟初始化
public class Singleton {
private static Singleton singleton;
// 私有化构造方法
private Singleton () {}
public static Singleton getInstance() {
if (singleton == null) {
// 1、多线程并发创建对象时,会通过加锁保证只有一个线程能创建对象
synchronized (Singleton.class) {
// 隐患:多线程环境下,由于重排序,该对象可能还未完成初始化就被其他线程读取
if (singleton == null) {
singleton = new Singleton();
}
}
}
// 2、对象创建完毕,执行getInstance() 将不需要获取锁,直接返回创建对象
return singleton;
}
}
在单线程环境下(或者说正常情况下),在“问题代码处”,会执行如下操作,保证能获取到已完成初始化的实例
memroy = alocate(); //1:分配对象的内存空间
ctorInstance(memory); //2:初始化对象
instance = memory; //3:设置 instance 指向刚分配的内存地址
但在多线程环境下,在“问题代码处”,可能会执行如下操作,由于重排序导致 2,3 乱序,后果就是其他线程得到的是null而不是完成初始化的对象
4、DCL双端锁
public class Singleton {
// 通过 volatile 声明,实现线程安全的延迟初始化。
private volatile static Singleton singleton;
// 私有化构造方法
private Singleton () {}
// 双重锁设计
public static Singleton getInstance() {
if (singleton == null) {
// 1、多线程并发创建对象时,会通过加锁保证只有一个线程能创建对象
synchronized (Singleton.class) {
// 隐患:多线程环境下,由于重排序,该对象可能还未完成初始化就被其他线程读取
// 解决隐患原理:利用volatile,禁止 指令重排!
if (singleton == null) {
singleton = new Singleton();
}
}
}
// 2、对象创建完毕,执行getInstance() 将不需要获取锁,直接返回创建对象
return singleton;
}
}
利用volatile,禁止 指令重排!