目录
[2.1 饿汉式](#2.1 饿汉式)
[2.2 懒汉式](#2.2 懒汉式)
[2.3 枚举(Enum)](#2.3 枚举(Enum))
[3.1 线程安全](#3.1 线程安全)
[3.2 反射攻击](#3.2 反射攻击)
[3.3 序列化与反序列化](#3.3 序列化与反序列化)
[3.4 克隆保护](#3.4 克隆保护)
1、核心思想
目的:确保一个类仅有一个实例
功能:供全局访问点(通过静态方法或变量提供全局访问入口),可以选择延迟加载。
优点 | 缺点 |
---|---|
严格控制实例数量,节省资源 | 可能隐藏类之间的依赖关系,降低可测试性 |
全局访问点方便管理共享资源 | 违背单一职责原则(管理自身生命周期) |
避免频繁创建销毁对象 | 多线程环境需额外处理同步问题 |
2、实现方式
2.1 饿汉式
饿汉式:即在初始阶段就主动进行实例化,并时刻保持一种渴求的状态,无论此单例是否有人使用。
特点:类加载时立即创建实例,线程安全但可能浪费资源。
java
public class EagerSingleton {
// static:在类加载时初始化,与类同在; final:一旦被赋值不能被更改
private static final EagerSingleton instance = new EagerSingleton();
// 私有构造函数,禁止外部调用创建对象
private EagerSingleton() {}
public static EagerSingleton getInstance() {
return instance;
}
}
2.2 懒汉式
懒汉式:在第一次使用时,再进行初始化,防止没有人调用,提前初始化造成的资源浪费。
特点:延迟实例化,需处理线程安全问题。
1> 同步锁版本(不推荐)
java
public class SynchronizedSingleton {
private static SynchronizedSingleton instance;
private SynchronizedSingleton() {}
public static synchronized SynchronizedSingleton getInstance() {
if (instance == null) {
instance = new SynchronizedSingleton();
}
return instance;
}
}
注意: 变量不能使用final,会导致被初始化为null,之后不可赋值
缺点:线程还没进入方法内,就先加锁排队,造成线程阻塞,资源和时间的浪费。
2> 双重检查锁**(Double-Checked Locking)**
java
public class DCLSingleton {
// volatile:防止指令重排序,保证变量值在各线程访问时的同步性、唯一性
private static volatile DCLSingleton instance;
private DCLSingleton() {}
public static DCLSingleton getInstance() {
if (instance == null) { // 第一次检查,放宽入口,保证线程并发高效性
synchronized (DCLSingleton.class) {
if (instance == null) { // 第二次检查,加锁同步,保证实例化单次运行
instance = new DCLSingleton();
}
}
}
return instance;
}
}
相比"懒汉模式",其实在大多数情况下我们通常会更多地使用"饿汉模式",原因在于这个单例迟早是要被实例化占用内存的,延迟懒加载的意义并不大,加锁解锁反而是一种资源浪费,同步更是会降低CPU的利用率,使用不当的话反而会带来不必要的风险。
2.3 枚举(Enum)
特点:天然防止反射和序列化攻击,线程安全(推荐方式)
java
public enum EnumSingleton {
INSTANCE; // 这是一个单例对象
public void doSomething() {
// 业务方法
}
}
3、关键注意事项
3.1 线程安全
多线程环境下需确保实例唯一性(如双重检查锁、枚举等)。
3.2 反射攻击
通过反射可绕过私有构造函数,需额外防护(如枚举或抛出异常)。
攻击示例:
java
// 反射攻击代码:
Class<?> clazz = Singleton.class;
Constructor<?> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true); // 绕过 private 权限
Singleton hackedInstance = (Singleton) constructor.newInstance(); // 创建新实例!
防御方法:在构造方法中抛异常( 检测是否已存在实例,若存在则抛出异常**)**
java
private Singleton() {
if (INSTANCE != null) {
throw new IllegalStateException("单例已被创建,禁止反射调用!");
}
}
3.3 序列化与反序列化
反序列化可能创建新实例,需实现 readResolve()
方法。
攻击示例:
java
// 序列化攻击代码:
Singleton instance1 = Singleton.getInstance();
// 序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(instance1);
// 反序列化
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
Singleton instance2 = (Singleton) ois.readObject(); // 新实例!
防御方法:**实现 readResolve()
方法,**直接返回已有实例,覆盖反序列化逻辑。
java
public class Singleton implements Serializable {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
// 反序列化时直接返回 INSTANCE
protected Object readResolve() {
return INSTANCE;
}
}
3.4 克隆保护
重写 clone()
方法并抛出异常。
若单例类实现了 Cloneable
接口,并直接使用默认的 clone()
方法(或自定义实现未做防御),攻击者可以通过调用 clone()
创建新实例,破坏单例的唯一性。
如果类不实现 Cloneable
接口,调用 clone()
会抛出 CloneNotSupportedException。
即使类未实现 Cloneable
,也可显式重写 clone()
方法禁止克隆:
java
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
// 防御 clone 攻击
@Override
protected Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException("禁止克隆单例!");
}
}
4、适用场景
-
资源共享:如数据库连接池、线程池。
-
配置管理:全局配置类避免重复加载。
-
日志记录:统一日志写入入口。
-
设备驱动:如打印机唯一控制实例。