设计模式-单例模式

单例模式(Singleton Pattern)是一种创建型设计模式,其核心目标是确保一个类只有一个实例,并提供全局访问点。

单例模式的核心实现要点

  1. 私有构造函数 :防止外部直接通过 new 创建实例。
  2. 静态实例:保存类的唯一实例。
  3. 全局访问点:提供获取实例的静态方法。
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;
    }
}

反射的核心功能

  1. 运行时获取类信息:无需提前知道类名,动态加载类。
  2. 操作对象:动态创建对象、调用方法、访问字段。
  3. 突破访问限制 :访问私有(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;
    }
相关推荐
码农老起28 分钟前
与Aspose.pdf类似的jar库分享
java·pdf·jar
程序猿小D1 小时前
第三百八十九节 JavaFX教程 - JavaFX WebEngine
java·eclipse·intellij-idea·vr·javafx
Alfadi联盟 萧瑶1 小时前
Python-Django入手
开发语言·python·django
三金C_C2 小时前
单例模式解析
单例模式·设计模式·线程锁
-代号95272 小时前
【JavaScript】十二、定时器
开发语言·javascript·ecmascript
勘察加熊人2 小时前
c++实现录音系统
开发语言·c++
self-discipline6343 小时前
【Java】Java核心知识点与相应面试技巧(七)——类与对象(二)
java·开发语言·面试
wei3872452323 小时前
java笔记02
java·开发语言·笔记
zjj5873 小时前
Docker使用ubuntu
java·docker·eureka
士别三日&&当刮目相看3 小时前
JAVA学习*简单的代理模式
java·学习·代理模式