单例模式(Singleton Pattern)是一种创建型设计模式,其核心目标是确保一个类只有一个实例,并提供全局访问点。
单例模式的核心实现要点
- 私有构造函数 :防止外部直接通过
new
创建实例。 - 静态实例:保存类的唯一实例。
- 全局访问点:提供获取实例的静态方法。
1、饿汉式
特点:类加载时直接初始化实例,线程安全,但可能浪费资源。
java
/**
* 饿汉式单例
* 优点:实现简单,线程安全
* 缺点:类加载时就初始化实例,可能浪费内存
*/
public class EagerSingleton {
// 静态常量直接初始化实例,保证线程安全
private static final EagerSingleton INSTANCE = new EagerSingleton();
// 私有构造函数,防止外部实例化
private EagerSingleton() {}
// 全局访问点
public static EagerSingleton getInstance() {
return INSTANCE;
}
}
2、懒汉式
特点:延迟加载实例,但是需要通过同步锁保证线程安全(效率低)。
java
/**
* 懒汉式单例(同步方法)
* 优点:延迟加载,节省资源
* 缺点:同步锁导致性能下降
*/
public class LazySingleton {
private static LazySingleton instance;
// 私有构造函数
private LazySingleton() {}
// 同步方法保证线程安全,但每次调用都加锁
public static synchronized LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
3、双重校验
特点:在懒汉式的基础上,降低锁的粒度,提高效率
java
/**
* 双重检查锁定单例
* 特点:在懒汉式的基础上,降低锁的粒度,减少锁的等待时间
*/
public class Singleton_03 {
// volatile 禁止指令重排序,保证可见性
private static volatile Singleton_03 instance;
private Singleton_03() {
}
public static Singleton_03 getInstance() {
// 第一次检查(无锁)
if (instance == null) {
synchronized (Singleton_03.class) {
// 第二次检查(有锁)
if (instance == null) {
instance = new Singleton_03();
/*
* new 操作分为三步:
* 1. 分配内存空间
* 2. 初始化对象
* 3. 将引用指向内存地址
* volatile 防止指令重排序到1 → 3 → 2 的情况
*/
}
}
}
//如果没有volatile 在多线程环境下会返回一个次品对象
return instance;
}
}
4、静态内部类
特点:利用类加载机制保证线程安全,天然支持延迟加载。
java
/**
* 静态内部类单例
* 优点:线程安全、延迟加载、无锁高效
* 缺点:无法通过参数初始化实例
*/
public class InnerClassSingleton {
// 私有构造函数
private InnerClassSingleton() {
}
// 静态内部类持有实例
private static class SingletonHolder {
private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
}
// 全局访问点
public static InnerClassSingleton getInstance() {
return SingletonHolder.INSTANCE; // 首次调用时加载内部类
}
}
5、枚举
特点:简洁、线程安全,天然防御反射和序列化攻击(推荐方式)。
java
/**
* 枚举单例(推荐)
* 优点:天然线程安全,防御反射和序列化攻击
* 缺点:无法延迟加载
*/
public enum EnumSingleton {
INSTANCE; // 枚举常量即为单例实例
public static EnumSingleton getInstance(){
return INSTANCE;
}
}
反射的核心功能
- 运行时获取类信息:无需提前知道类名,动态加载类。
- 操作对象:动态创建对象、调用方法、访问字段。
- 突破访问限制 :访问私有(
private
)成员。
1. 防御反射攻击
- 问题:通过反射调用私有构造函数可以创建新实例。
- 解决方案:在构造函数中检查实例是否已存在,若存在则抛出异常。
2. 防御序列化攻击
- 问题:反序列化会生成新对象。
- 解决方案 :实现
readResolve()
方法返回已有实例。
反射对于单例的破坏
java
/**
* 反射对于单例的破坏
*/
public class Test_Reflect {
public static void main(String[] args) throws Exception {
Class<InnerClassSingleton> clazz = InnerClassSingleton.class;
Constructor<InnerClassSingleton> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
InnerClassSingleton instance = constructor.newInstance();
InnerClassSingleton instance2 = constructor.getInstance();
System.out.println(instance == instance2);
}
}
false
解决方式
在构造器添加一个判断语句
java
// 私有构造函数
private InnerClassSingleton() {
// 防御反射攻击
if (SingletonHolder.INSTANCE != null) {
throw new RuntimeException("单例对象已存在!");
}
}
序列化对于单例的破坏
java
public class Test_Serializable {
@Test
public void test() throws Exception{
//序列化对象输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile.obj"));
oos.writeObject(InnerClassSingleton.getInstance());
//反序列化对象输入流
File file = new File("tempFile.obj");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
InnerClassSingleton instance = (InnerClassSingleton) ois.readObject();
System.out.println(instance);
System.out.println(InnerClassSingleton.getInstance());
System.out.println(instance == InnerClassSingleton.getInstance());
}
}
//输出
com.pgs.singleton.demo04.Singleton_04@3c9d0b9d
com.pgs.singleton.demo04.Singleton_04@6dbb137d
false
解决方式
只需要在单例类中定义 readResolve 方法,就可以解决序列化对于单例的破坏
java
// 防止反序列化破坏单例
private Object readResolve() {
return SingletonHolder.INSTANCE;
}