单例模式
- 创建型设计模式
-
什么是创建型设计模式
- 限制一个类只有一个实例
一、饿汉式
- 在类加载时就创建了实例
- 浪费资源, 在类加载时就创建实例,如果该实例在后续没有被使用到,就会造成资源的浪费
- 线程安全,因为类加载是安全的
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {} // 私有构造函数防止类被外部实例化
public static Singleton getInstance() {
return instance;
}
}
instance 要定义为 static 才可以在 getInstance() 里访问到
二、懒汉式
- 延迟加载/懒加载:在需要使用实例时才进行的实例的创建
- 线程不安全,多个线程可能同时判断instance == null;可以采用双重检查或者静态内部类的方式来实现线程安全
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
2.1 双重检查懒汉式
-
第一次检查的目的?
-
只有第二次检查?没有第一次检查?
-
是为了避免在实例已经创建的情况下仍然执行同步块,减少开销,提高性能
-
第二次检查的目的是?
-
只有第一次检查?没有第二次检查?
-
第二次检查的目的是保证单例,假设没有第二次检查,线程1先通过第一次检查判断实例为空,进入到同步代码块,在线程1还没将实例创建出来前,线程2已经通过第一次检查判断到实例为空,等待线程1创建完实例释放锁,线程1创建完实例释放锁后,线程2进到同步代码块,因为没有第二次检查,所以线程2也创建了个实例,此时就有两个实例存在,就不是单例了
-
为什么使用volatile
-
我们在单例模式中使用
volatile
,主要是使用volatile
可以禁止指令重排序,从而保证程序的正常运行,volatile
关键字禁止编译器和处理器对被修饰变量的指令重排序,从而确保了实例化对象的有序性。在懒汉式单例模式中,如果不使用volatile
关键字修饰单例对象的引用变量,可能会导致在第一个线程创建实例时,由于指令重排序,第二个线程可能会在实例还未完全初始化时就获取到该引用,从而访问到一个不完整的对象。
// 双重检查机制
public class LazySingleton {
private static volatile LazySingleton instance;
private LazySingleton() {
// 私有构造方法
}
public static LazySingleton getInstance() {
if (instance == null) { // 第一次检查
synchronized (LazySingleton.class) {
if (instance == null) { // 第二次检查
instance = new LazySingleton();
}
}
}
return instance;
}
}
2.2静态内部类懒汉式
TODO
// 静态内部类方式
public class LazySingleton {
private LazySingleton() {
// 私有构造方法
}
private static class SingletonHolder {
private static final LazySingleton instance = new LazySingleton();
}
public static LazySingleton getInstance() {
return SingletonHolder.instance;
}
}
三、枚举
- 简单而且线程安全
- 推荐使用的方式
public enum EnumSingleton {
INSTANCE; // 唯一的枚举常量
// 枚举类的成员和方法
// 你可以在这里添加其他成员和方法
}