单例模式(Singleton Pattern)是一种创建型设计模式,其核心目标是确保一个类只有一个实例,并提供全局访问点。
单例模式的核心实现要点
- 私有构造函数:防止外部直接通过
new
创建实例。 - 静态实例:保存类的唯一实例。
- 全局访问点:提供获取实例的静态方法。
1、饿汉式
特点:类加载时直接初始化实例,线程安全,但可能浪费资源。
/*** 饿汉式单例* 优点:实现简单,线程安全* 缺点:类加载时就初始化实例,可能浪费内存*/public class EagerSingleton {// 静态常量直接初始化实例,保证线程安全private static final EagerSingleton INSTANCE = new EagerSingleton();// 私有构造函数,防止外部实例化private EagerSingleton() {}// 全局访问点public static EagerSingleton getInstance() {return INSTANCE;}}
2、懒汉式
特点:延迟加载实例,但是需要通过同步锁保证线程安全(效率低)。
/*** 懒汉式单例(同步方法)* 优点:延迟加载,节省资源* 缺点:同步锁导致性能下降*/
public class LazySingleton {private static LazySingleton instance;// 私有构造函数private LazySingleton() {}// 同步方法保证线程安全,但每次调用都加锁public static synchronized LazySingleton getInstance() {if (instance == null) {instance = new LazySingleton();}return instance;}
}
3、双重校验
特点:在懒汉式的基础上,降低锁的粒度,提高效率
/*** 双重检查锁定单例* 特点:在懒汉式的基础上,降低锁的粒度,减少锁的等待时间*/
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、静态内部类
特点:利用类加载机制保证线程安全,天然支持延迟加载。
/*** 静态内部类单例* 优点:线程安全、延迟加载、无锁高效* 缺点:无法通过参数初始化实例*/
public class InnerClassSingleton {// 私有构造函数private InnerClassSingleton() {}// 静态内部类持有实例private static class SingletonHolder {private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();}// 全局访问点public static InnerClassSingleton getInstance() {return SingletonHolder.INSTANCE; // 首次调用时加载内部类}
}
5、枚举
特点:简洁、线程安全,天然防御反射和序列化攻击(推荐方式)。
/*** 枚举单例(推荐)* 优点:天然线程安全,防御反射和序列化攻击* 缺点:无法延迟加载*/
public enum EnumSingleton {INSTANCE; // 枚举常量即为单例实例public static EnumSingleton getInstance(){return INSTANCE;}
}
反射的核心功能
- 运行时获取类信息:无需提前知道类名,动态加载类。
- 操作对象:动态创建对象、调用方法、访问字段。
- 突破访问限制:访问私有(
private
)成员。
1. 防御反射攻击
- 问题:通过反射调用私有构造函数可以创建新实例。
- 解决方案:在构造函数中检查实例是否已存在,若存在则抛出异常。
2. 防御序列化攻击
- 问题:反序列化会生成新对象。
- 解决方案:实现
readResolve()
方法返回已有实例。
反射对于单例的破坏
/*** 反射对于单例的破坏*/
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
解决方式
在构造器添加一个判断语句
// 私有构造函数private InnerClassSingleton() {// 防御反射攻击if (SingletonHolder.INSTANCE != null) {throw new RuntimeException("单例对象已存在!");}}
序列化对于单例的破坏
public class Test_Serializable {@Testpublic 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 方法,就可以解决序列化对于单例的破坏
// 防止反序列化破坏单例private Object readResolve() {return SingletonHolder.INSTANCE;}