首页 > 其他分享 >单例模式

单例模式

时间:2022-09-07 21:59:33浏览次数:82  
标签:Singleton 模式 INSTANCE static 单例 declaredConstructor class

单例就是只有一个实例对象,即在整个程序中,同一个类始终只有一个对象进行操作。这样可以极大的减少内存开支和系统的性能开销。这种模式提供了一种创建对象的最佳方式,让类负责创建自己的对象,同时确保只有单个对象被创建。这个类需要提供访问其唯一对象的方式,且可以直接访问,不需要实例化该类的对象

  • 为保证只能由自己创建对象,单例类必须构造方法私有化
  • 单例类必须自己创建自己的唯一实例
  • 单例类必须给所有其他对象提供这一实例
  • 经过多年的演进,单例模式有诸多实现方式,下面逐个介绍

饿汉式

饿汉式单例是最普通的单例模式写法,由于在类加载时就创建对象,保证了线程的安全。这种方式比较常用,但容易产生垃圾对象,对空间的消耗较大,但是不存在线程安全问题, 因此不用加锁,效率较高,以空间换时间。

public class Singleton {
    //单例模式的核心,构造方法私有化
    private Singleton() {
    }
    //用于全局引用的唯一单例对象,在一开始就创建好
    private final static Singleton INSTANCE = new Singleton();
    //获取全局唯一的单例对象
    public static Singleton getInstance() {
        return INSTANCE;
    }
}

懒汉式

双重检测锁(DCL,即 double-checked locking)懒汉式单例模式

  • 添加synchronized关键字规避多线程情况
  • INSTANCE = new Singleton(); 不是原子性操作,需要增加 volatile 关键字来避免指令重排
    • 分配对象内存空间
    • 执行构造方法初始化对象
    • 设置 instance 指向刚分配的内存地址,此时 instance != null
public class Singleton {
    private Singleton() {
    }
    //在一开始先不进行对象创建
    private volatile static Singleton INSTANCE;

    public static Singleton getInstance(){
        // 外层检测规避多线程情况
        if(INSTANCE == null) {
            // 只对赋值这一步进行加锁,提升效率
            synchronized (Singleton.class) {
                // 内层检测以实现单例
                if(INSTANCE == null) {
                    INSTANCE = new Singleton();
                }
            }
        }
        return INSTANCE;
    }
}

静态内部类实现

静态内部类实现依赖于 Java 在类加载时不会加载其内部类,只有实例化内部类时才会加载的特性。其他语言不具备这一特性,无法使用该方式实现,因此这种方式不具备通用性。

public class Singleton {
    private Singleton() {
    }
    /**
     * 由静态内部类持有单例对象
     * <p>根据类加载特性,仅使用外部类时,不会对静态内部类进行初始化
     */
    private static class Holder {
        private final static Singleton INSTANCE = new Singleton();
    }
    /**
     * 只有真正使用内部类时,才会进行类初始化
     * @return 单例对象
     */
    public static Singleton getInstance(){
        // 直接获取内部类中的对象
        return Holder.INSTANCE;
    }
}

利用反射破坏单例

  • 反射可以无视 private 修饰的构造方法,可以直接在外面 newInstance,破坏单例

    Singleton singleton1 = Singleton.getInstance();
    // 获取无参构造器
    Constructor<Singleton> declaredConstructor = Singleton.class.getDeclaredConstructor(null);
    // 取消无参构造器私有属性
    declaredConstructor.setAccessible(true);
    // 使用反射获取的无参构造器实例化对象
    Singleton singleton2 = declaredConstructor.newInstance();
    // 比较实例化的两个对象
    System.out.println(singleton1 == singleton2);	//输出结果为 false
    
  • 可以在构造方法中加上对象的非空判断,但如果先用反射创建对象,判断就不生效了

    private Singleton() {
        synchronized (Singleton.class) {
            if (INSTANCE != null) {
                throw new RuntimeException("不要试图用反射破坏单例");
            }
        }
    }
    
  • 使用标志位来避免重复创建:

    private static boolean flag = false;
    private Singleton() {
        synchronized (Singleton.class) {
            if (flag == false) {
                // 第一次创建后,标志位设为 true
                flag = true;
            } else {
                // 后续再使用构造方法创建则直接报错
                throw new RuntimeException("不要试图用反射破坏单例模式");
            }
        }
    }
    

    但还是不能完全防止反射破坏单例,因为可以利用反射修改 flag 的值。

    Constructor<Singleton> declaredConstructor = Singleton.class.getDeclaredConstructor(null);
    declaredConstructor.setAccessible(true);
    Singleton singleton1 = declaredConstructor.newInstance();
    // 获取 flag 属性
    Field field = Singleton.class.getDeclaredField("flag");
    // 取消 flag 属性的私有属性
    field.setAccessible(true);
    // 通过反射,修改属性的值
    field.set(singleton1, false);
    Singleton singleton2 = declaredConstructor.newInstance();
    System.out.println(singleton1 == singleton2);	//输出结果为 false
    

枚举实现

public enum Singleton {
    INSTANCE
}

尝试用反射进行破坏:

Singleton instance1 = Singleton.INSTANCE;
Constructor<Singleton> declaredConstructor = Singleton.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
Singleton instance2 = declaredConstructor.newInstance();
System.out.println(instance1 == instance2);

运行报错:

Exception in thread "main" java.lang.NoSuchMethodException: cn.sail.singleton.enums.Singleton.<init>()
    at java.lang.Class.getConstructor0(Class.java:3082)
    at java.lang.Class.getDeclaredConstructor(Class.java:2178)

查看编译后的 Singleton.class 文件,是存在无参构造的, 再使用jad进行反编译,可以看到,确实是没有无参构造,而是存在一个有参构造

public final class Singleton extends Enum
{
    public static Singleton[] values()
    {
        return (Singleton[])$VALUES.clone();
    }
    public static Singleton valueOf(String name)
    {
        return (Singleton)Enum.valueOf(cn/sail/singleton/enums/Singleton, name);
    }
    private Singleton(String s, int i)
    {
        super(s, i);
    }
    public static final Singleton INSTANCE;
    private static final Singleton $VALUES[];
    static 
    {
        INSTANCE = new Singleton("INSTANCE", 0);
        $VALUES = (new Singleton[] {
            INSTANCE
        });
    }
}

按照有参构造再使用反射创建:

Singleton instance1 = Singleton.INSTANCE;
Constructor<Singleton> declaredConstructor = Singleton.class.getDeclaredConstructor(String.class, int.class);
declaredConstructor.setAccessible(true);
Singleton instance2 = declaredConstructor.newInstance();
System.out.println(instance1 == instance2);

此时运行也报错,但报的错不一样了,这就是希望得到的结果:不能用反射破坏枚举:

Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects

该报错信息是在反射中 Constructor 的 newInstance() 源码,如果反射类为枚举直接抛出该异常:

@CallerSensitive
public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
    if (!override) {
        if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
            Class<?> caller = Reflection.getCallerClass();
            checkAccess(caller, clazz, null, modifiers);
        }
    }
    if ((clazz.getModifiers() & Modifier.ENUM) != 0)
        throw new IllegalArgumentException("Cannot reflectively create enum objects");
    ConstructorAccessor ca = constructorAccessor;
    if (ca == null) {
        ca = acquireConstructorAccessor();
    }
    @SuppressWarnings("unchecked")
    T inst = (T) ca.newInstance(initargs);
    return inst;
}

标签:Singleton,模式,INSTANCE,static,单例,declaredConstructor,class
From: https://www.cnblogs.com/bkyxiaozhi/p/16667386.html

相关文章

  • BigDecimal 舍入模式
    最近写了一个关于金额计算的需求,用户对精度要求比较高所以使用到Bigdecimal,又涉及到范围限制,所以就涉及到如果除不尽是Bigdecimal的舍人模式的问题:舍人模式RoundingMode是......
  • 13.1 反射 13.2selenium键盘事件13.3文件上传13.4滚动条操作 13.5鼠标事件13.6验证码
    13.1反射#什么是反射?#如果有一个变量名,是字符串的数据类型,你能获取到这个变量的值吗?#反射方法:classStudent:def__init__(self):self.name='张三'......
  • 设计模式_多例模式
    看个例子:有个线程池有五个线程,每次随机使用一个? 定义模拟线程类【SimulationThread】,定义线程名变量【threadName】,定义线程运行方法【runThread】。   定义线程......
  • PostgreSQL数据库、模式、表、空间、用户间的关系
    PostgreSQL数据库、模式、表、空间、用户间的关系(1)DB实例与schema:模式是数据库实例的逻辑分割。数据库是被模式(schema)来切分的,一个数据库至少有一个模式,所有数据库......
  • 通过预绑定方法模式实现单例模式
    什么是预绑定方法模式预绑定方法模式是一种将对象方法绑定为函数的模式。要实现该模式,只需要一个单例对象----模块(module)当你在python中执行import语句导入模块时,无论i......
  • 原型模式
    原型模式原型接口packageprototypetypeCloneableinterface{clone()Cloneable}执行深拷贝的原型对象packageprototypetypeDeepstruct{namestr......
  • ACM模式各种输入总结 C++
    一、整型数组输入:(很简单)在终端的一行中输入固定数目的整型数字,并存到数组中,中间以空格分隔。示例:3123intn;cin>>n;vector<int>nums(n);......
  • 设计模式_单例模式
    先看个例子:公司中只能有一个老板。 定义【Boss】类,定义属性【name】。   执行结果:  总结:单例模式主要用于不同线程之间的并发访问或者通信,也可以达到节约资源......
  • 设计模式
    设计模式您是否曾经在一个团队中对如何实现某些功能没有清晰的想法?这是开发人员的常见情况,所以很多时候我们都在处理类似的功能,总是使用“对象”定义,处理表单的新屏幕,或者......
  • 【设计模式】Java设计模式 - 原型模式
    【设计模式】Java设计模式-原型模式......