【设计模式】单例模式

本文详细介绍了单例模式的基本概念、优缺点以及五种常见的实现方式,包括饿汉式、懒汉式、双重检测锁、静态内部类和枚举式。此外,针对反射和序列化可能破坏单例模式的问题,提出了相应的解决方案,如在构造方法中添加异常处理和实现`readResolve()`方法来防止反序列化漏洞。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一、基本介绍

二、代码实现

三、解决反射、序列化破解上面几种实现方式的漏洞(除枚举式外)

 ①反射漏洞

 ②反序列化漏洞


一、基本介绍

核心作用:保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。

优点:1. 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他  依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。

           2.单例模式可以在系统设置全局的访问点,优化环共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理.

常见的五种单例模式实现方式:

                   主要有:饿汉式(天然的线程安全,调用效率高。 但是,不能延时加载。) 

                                 懒汉式(需加锁保证线程安全,调用效率不高。 但是,可以延时加载。)

                    其  他:双重检测锁式(由于JVM底层内部模型原因,偶尔会出问题。不建议使用) 

                                 静态内部类式(线程安全,调用效率高。 可以延时加载) 

                                 枚举单例(线程安全,调用效率高,不能延时加载)

二、代码实现

①:饿汉式(天然的线程安全,调用效率高,不可以延迟加载)

       优点:天然的线程安全不用加锁调用效率高。

       缺点:在类加载初始化时就创建好一个静态的对象供外部使用(立即加载),可能未被使用导致资源利用率低

public class SingletonDemo01 {
    private static SingletonDemo01 instance = new SingletonDemo01();
    //私有构造方法,禁止被new对象
    private SingletonDemo01(){
    }
    public static SingletonDemo01 getInstance(){
        return instance;
    }
}

②:懒汉式(需加锁保证线程安全,调用效率低,可以延迟加载)

       有点:可以延迟加载,只有当被使用才会构建实例,资源利用率高

       缺点:在方法上加synchronized同步锁,此种方式虽然解决了多个实例对象问题,但是该方式运行效率却很低下,下一个线程想要获取对象,就必须等待上一个线程释放锁之后,才可以继续运行。

public class SingletonDemo02 {
    //不初始化,延迟加载
    private static SingletonDemo02 instance = null;
    //私有构造方法,禁止被new对象
    private SingletonDemo02(){
    }
    public static synchronized SingletonDemo02 getInstance(){
        if(instance==null){
            instance = new SingletonDemo02();
        }
        return instance;
    }
}

 ③:双重检测锁

        改良了懒汉式加载直接锁整个方法,只对需要锁的代码部分加锁,调用效率比懒汉式高

        问题:由于编译器优化原因和JVM底层内部模型原因,偶尔会出问题,不建议使用 

        问题详细描述:在new一个对象时 ,会分配一块内存、在内存里赋以默认值、init初始化、将初始化完后的对象引用返回,在这个过程中如果编译器进行了重排序就会导致其实已经new了一个SingletonDemo03,只不过此时默认值为null,冉后就将其的引用返回,之后在进行初始化,这样就有可能后续其他线程调用getInstance()方法刚好这个时候进来,就无法保证单例性。

public class SingletonDemo03 {
    //不初始化,延迟加载
    private static SingletonDemo03 instance = null;
    //私有构造方法,禁止被new对象
    private SingletonDemo03(){
    }
    public static SingletonDemo03 getInstance(){
        if(instance==null){
            synchronized(SingletonDemo03.class){
                if (instance==null)
                    instance = new SingletonDemo03();
            }
        }
        return instance;
    }
}

          解决方案:加volatile关键字修饰变量,因为volatile可以禁止重排序。

④:静态内部类的实现式

       优点:提高资源利用率(静态内部类只有被调用的时候才会被加载,所以该模式可以延迟加载)

                  线程安全(instance是static final类型,保证了内存中只有这样一个实例存在,只能被赋值一次,保证了线程安全性)

public class SingletonDemo04 {
   //私有构造方法,禁止被new对象
    private SingletonDemo04(){
    }
    public static SingletonDemo04 getInstance(){
       return SingletonInstance.instance;
    }

    public static class SingletonInstance{
        private static final SingletonDemo04 instance = new SingletonDemo04();
    }
}

⑤:枚举式

       优点:实现简单、枚举本身就是单例模式,由JVM提供根本保障,避免了通过反射和反序列化的漏洞。

       缺点:无法延迟加载

       当其他类使用EnumDemo demo = EnumDemo.INSTANCE;构建一个实例时,这个EnumDemo枚举类的构造函数永远只会被加载一次,所以直接就是单例模式!

public enum EnumDemo {
    //这个枚举元素,本身就是单例对象
    INSTANCE;

    //添加其他需要的操作
    public void OtherOperation() {
    }
}

三、解决反射、序列化破解上面几种实现方式的漏洞(除枚举式外)

 ①反射漏洞

       如下代码是使用反射出SingletonDemo01即饿汉式单例模式,尽管饿汉式单例模式私有化了构造方法,但还是通过反射方式创建出新的实例对象。

       其中c.setAccessible(true);是将c对应的Java类私有构造器设置成可进入的

public class test {
    public static void main(String[] args) throws Exception{
        Class<SingletonDemo01> clazz = (Class<SingletonDemo01>)Class.forName
                                       ("com.design.priciple.Singleton.SingletonDemo01");
        Constructor<SingletonDemo01> c = clazz.getDeclaredConstructor();
        //将类的构造方法设置成可进入的   
        c.setAccessible(true);
        SingletonDemo01 s1 = c.newInstance();
        System.out.println("s1="+s1);
    }
}

 如何结果呢?  其实很简单只需要在构造方法中抛出异常即可!

      下面的代码是将饿汉式单例模式中的构造方法添加了一句if判断,当有其他类只能进入了SingletonDemo01类的构造方法就会抛出异常!

public class SingletonDemo01 {
    private static SingletonDemo01 instance = new SingletonDemo01();
    //私有构造方法,禁止被new对象
    private SingletonDemo01(){
        if(instance != null){
            throw new RuntimeException();
        }
    }
    public static SingletonDemo01 getInstance(){
        return instance;
    }
}

②反序列化漏洞

     当通过将饿汉式单例模式的实例对象序列化、反序列化后得到的会是另一个实例对象,与序列胡的对象不同

public class test{
    public static void main(String[] args) throws Exception{
        SingletonDemo01 s1 = SingletonDemo01.getInstance();
        //序列化
        FileOutputStream fos = new FileOutputStream("d:/a.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(s1);
        oos.close();
        fos.close();
        //反序列化
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/a.txt"));
        SingletonDemo01 s2 = (SingletonDemo01)ois.readObject();
        System.out.println(s1==s2);

    }
}

 解决方案:只需在单例模式代码中加入一个readResolve()方法

                   该方法会在反序列化时,会直接调用这个方法返回instance对象,不会再返回新对象

public class SingletonDemo01 implements Serializable {
    private static SingletonDemo01 instance = new SingletonDemo01();
    //私有构造方法,禁止被new对象
    private SingletonDemo01(){
    }
    public static SingletonDemo01 getInstance(){
        return instance;
    }
    private Object readResolve(){
        return instance;
    }
}

                  加了radeResolve()方法后,再次运行代码输出"true" 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值