一、定义
单例模式是一种创建型模式。
单例模式确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。
1.1 单例模式的三个特点
- 单例类的构造函数必须是私有的。单例类有且仅有一个实例,故不能让调用方使用
new Class()
来创建实例,要把类的初始化权限控制在类的内部。 - 单例类通过一个私有的静态变量来存储唯一实例。
- 单例类通过提供一个公开的静态方法来供外部使用者可以访问到唯一实例。
1.2 单例模式的三个注意事项
- 创建实例时,需要考虑是否线程安全。
- 实例的创建是否需要延迟加载。
- 获取实例时,是否要加锁。
二、饿汉单例模式
饿汉单例模式在类加载时就立即进行初始化,创建单例对象。
- 优点
线程安全,实例化在线程出现之前,没有多线程同步问题。
不需要加锁。 - 缺点
没有用到的单例也会被创建,可能造成内存浪费。
案例
public class HungrySingleton {
private static final HungrySingleton singleton = new HungrySingleton();
private HungrySingleton() {}
public static HungrySingleton getInstance() {
return singleton;
}
}
三、懒汉单例模式
懒汉单例模式支持延迟加载,只有在第一次需要用到时才创建实例。不过也因此引入了线程安全问题,故在获取实例时需要加锁,这也导致性能降低。
- 优点
延迟加载,不造成内存浪费。 - 缺点
线程不安全,需要加锁。
因为加锁,影响并发性能。
案例
public class LazySingleton {
private static LazySingleton singleton;
private LazySingleton() {}
public synchronized static LazySingleton getInstance() {
if (singleton == null) {
System.out.println("=== 初始化 ===");
singleton = new LazySingleton();
}
return singleton;
}
}
案例:双重检查
其实只有在第一次创建实例时才需要加锁,之后的每次获取都是不需要加锁的,可以使用双重检查的方式进行优化。
- 优点
实例的创建时线程安全的。
支持延迟加载。
实例的获取无需加锁。
public class LazySingleton {
private static volatile LazySingleton singleton = null;
private LazySingleton() {}
public static LazySingleton getInstance() {
if (singleton == null) {
synchronized (LazySingleton.class) {
if (singleton == null) {
System.out.println("=== 初始化 ===");
singleton = new LazySingleton();
}
}
}
return singleton;
}
}