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