单例模式
字数: 0 字 时长: 0 分钟
简介
单例模式(Singleton pattern),它确保一个类只有一个实例,并且提供一个全局访问点。
优点:
- 节省资源:控制实例数量,避免在高频场景中频繁创建和销毁对象
- 统一管理:全局只有一个入口,方便管理,使用方便
- 线程安全:好的单例实现保证只创建一次,避免竞态条件
缺点:
- 不利于测试和扩展:单例很难在单元测试时替换成 mock 对象
- 潜在的状态问题:单例属于共享资源,一个地方修改它的状态,可能导致其他地方出现问题
- 生命周期不可控:单例通常在 JVM 生命周期内都存在,可能导致资源无法及时释放
UML 类图
- 私有化构造器:禁止外部通过构造器
new
新的对象实例(不过无法防止反射) - 唯一实例变量:通常写为
private static Singleton instance
- 全局访问点:提供一个全局访问点
public static Singleton getInstance()
- 线程安全:通过加锁、双检锁、静态内部类、枚举等实现方式保证线程安全
实现示例
1. 饿汉式
在类加载阶段就完成了实例的初始化,线程安全,但无法实现延迟加载。
java
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
2. 懒汉式
懒汉式在第一次访问时才创建实例(延迟加载),使用 synchronized
实现线程安全,但每次访问 getInstance()
都需要同步,性能较低。
java
public class Singleton {
private static Singleton INSTANCE;
private Singleton() {}
// 对访问方法加锁保证线程安全
public synchronized Singleton getInstance() {
if (INSTANCE == null){
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
3. 双检锁 (最佳实践)
既做到了延迟加载又保证了访问时的高性能,使用 volatile
防止指令重排序,双重 null
值检查,保证后续访问时无锁。
java
public class Singleton {
private static volatile Singleton INSTANCE;
private Singleton() {}
public static Singleton getInstance() {
if (INSTANCE == null){
synchronized (Singleton.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
4. 静态内部类
利用 JVM 在加载外部类时不会立即加载内部类的特性实现延迟加载,又借助类加载机制保证了线程安全。
java
public class Singleton {
private Singleton(){}
private static class SingletonHolder{
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
5. 枚举
《Effective Java》 中提出的枚举实现单例模式,枚举值在类加载时就创建,线程安全、天生防止反射破坏、自动处理序列化、简洁优雅。
java
public enum Singleton {
INSTANCE;
}
单例的破坏和防护
1. 反射防护
单例模式只是私有化了构造器,防止外部直接 new
创建实例,但是外部可以通过反射直接调用构造器创建实例。因此可以在构造方法中添加一个判断,如果实例已经创建了,则抛出一个异常,阻止外部创建实例。
java
public class Singleton {
private static boolean instantiated = false;
private Singleton(){
if(instantiated){
throw new IllegalStateException("已存在实例");
}
instantiated = true;
}
}
2. 序列化安全
本质还是反射破坏单例,因为反序列化时默认会通过反射调用私有的构造方法创建实例,破坏了单例。
readObject
方法是 JAVA 序列化机制中的一种特殊回调方法。当对象被反序列化时,若定义了 readObject
方法,则 JVM 会调用这个方法,用其返回值作为反序列化生成的对象。
java
public class Singleton implements Serializable {
private static final Singleton instance = new Singleton();
private Singleton() {}
protected Object readResolve() {
return instance;
}
}
3. 克隆防护
clone()
也可以破坏单例,可以参照放射防护的办法,重写 clone()
方法,在方法中抛出异常
java
public class Singleton implements Cloneable {
private static final Singleton instance = new Singleton();
private Singleton() {}
@Override
protected Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
}
Spring 单例
Spring 单例不同于传统单例模式,Spring 单例是容器单例,生命周期由容器管理,支持依赖注入(传统单例很难实现),通过 ConcurrentHashMap
实现线程安全。