zoukankan      html  css  js  c++  java
  • 单例模式(Singleton Pattern)

    单例模式:一个类仅有一个实例,并提供一个访问它的全局访问点。

    • 优点:减少代码冗余、提高代码复用性、安全性、隐藏真实角色、非入侵、节约内存、重复利用
    • 缺点:线程安全问题,数量很多的话容易导致内存泄露
      image.png

    应用场景

    • spring IOC容器
    • 线程池(数据库、多线程)
    • 枚举、常量类
    • 配置文件常量
    • 日志
    • HttpApplication、servlet
    • windows系统的任务管理器、回收站、网站计数器、显卡驱动、打印机等

    单例优缺点

    优点:
    a、防止其他对象自身的实例化,保证所有的对象都访问一个实例
    b、类在实例化进程上有一定的伸缩性
    c、提供了对唯一实例的受控访问
    d、节约系统创建和销毁对象的资源
    e、允许对共享资源的多重占用

    缺点:
    a、不适用于变化的对象
    b、没有抽象层,所以扩展难度很大
    c、职责过重,违背了单一职责原则
    d、滥用容易导出溢出或丢失情况

    单例模式的种类

    image.png
    image.png
    image.png
    image.png

    第一个if是判断实例对象是否存在,针对读写都有的操作。
    第二个if是对同时进行初始化操作的多个线程进入锁状态的再次判断,如果前面已经有创建过的话,将不再实例化,彻底解决单例问题。

    image.png

    静态内部类方式与双重检验锁的区别
    双重检验锁是采用了懒汉式并且只针对写操作加了锁,保证了线程的安全又加快了读的操作。但如果多线程进来的时候,写操作会存在阻塞的现象,效率不高。
    静态内部类是在使用时才会被初始化,但内部类又使用了饿汉式的模式,所以既拥有饿汉式的线程安全,又支持懒汉式的资源不浪费,不存在线程阻塞的情况,比双重检验锁更加的高效。

    image.png
    image.png

    单例模式创建方式如何选择

    • 如果不需要延迟加载单例,可以使用枚举或者饿汉式,相对来说枚举性好于饿汉式。
    • 如果需要延迟加载,可以使用静态内部类或者懒汉式,相对来说静态内部类好于懒汉式。

    单例模式如何破坏

    a、利用反射机制,通过declaredConstructors.setAccessible(true);方法即可破解构造函数私有化的缺陷

    Class<?> classInfo = Class.forName("com.jgspx.singleton.crack.regex.RegixObject");
    Constructor declaredConstructors = classInfo.getDeclaredConstructor();
    declaredConstructors.setAccessible(true);
    Object o = declaredConstructors.newInstance();
    
    • 破解:在构造函数里判断如果已经实例化的话就抛出异常,防止多次被实例化
    public class RegixObject {
        /**
         * 如果是饿汉式,则反射机制调用构造函数的时候就会报错
         */
        private static RegixObject regixObject = new RegixObject();
    
        public static  RegixObject getInstance(){
            /**
             * 如果是懒汉式,先利用反射,然后代码调用,则构造函数里加上判断也没用
             */
            if(regixObject==null){
                regixObject = new RegixObject();
            }
            return regixObject;
        }
        private RegixObject(){
            //加上这一句话,可破解多次初始化的情况
            if(regixObject!=null){
                throw new RuntimeException("初始化已执行过");
            }
        }
    }
    

    b、利用序列化,将序列化后的结果存入硬盘,然后再次反序列化,等到的结果就和原先的不一致

    序列化:将存放在内存中的对象信息序列操作后变成可以存放在硬盘的数据。
    反序列化:将硬盘上的数据解析后放入到内存中。

    public static void main(String[] args) throws Exception{
        User instance = User.getInstance();
        FileOutputStream fos = new FileOutputStream("./user.obj");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(instance);
        oos.flush();
        oos.close();
    
        FileInputStream fis = new FileInputStream("./user.obj");
        ObjectInputStream ois = new ObjectInputStream(fis);
        User singleton2 = (User) ois.readObject();
        System.out.println(singleton2==instance);
    }
    
    • 破解:在原有类中增加方法readResolve()
    public class User implements Serializable {
        public static User user = new User();
    
        public static User getInstance(){
            return  user;
        }
    
        //返回序列化获取对象 ,保证为单例
        public Object readResolve() {
            return user;
        }
    }
    
    • ObjectInputStream
    • case TC_OBJECT:return checkResolve(readOrdinaryObject(unshared)); *
    • ObjectStreamClass desc = readClassDesc(false);---获取当前类的超类(没有实现Serializable的)。eg:User extend A,且A没有实现Serializable,则desc=A.class,否则desc=Object.class
    • if (desc.hasReadResolveMethod()){Object rep = desc.invokeReadResolve(obj);}

    最强单例模式--枚举

    a. 枚举单例源码

    public enum  SingleV6 {
        TT;
    
        SingleV6(){
            System.out.println("我是无参构造函数被执行到了");
        }
    
        public void add(){
            System.out.println("添加操作被启动");
        }
    }
    

    b. 枚举单例反编译后的代码

    public final class SingleV6 extends Enum
    {
    
        public static SingleV6[] values()
        {
            return (SingleV6[])$VALUES.clone();
        }
    
        public static SingleV6 valueOf(String name)
        {
            return (SingleV6)Enum.valueOf(com/jarye/singleton/v6/SingleV6, name);
        }
    
        private SingleV6(String s, int i)
        {
            super(s, i);
            System.out.println("u6211u662Fu65E0u53C2u6784u9020u51FDu6570u88ABu6267u884Cu5230u4E86");
        }
    
        public void add()
        {
            System.out.println("u6DFBu52A0u64CDu4F5Cu88ABu542Fu52A8");
        }
    
        public static final SingleV6 TT;
        private static final SingleV6 $VALUES[];
    
        static 
        {
            TT = new SingleV6("TT", 0);
            $VALUES = (new SingleV6[] {
                TT
            });
        }
    }
    

    枚举类型其实就是class类,继承于Enum类,内置了name、ordinal和values方法,且没有默认的无参构造函数。
    A、底层转换类继承Enum
    B、使用静态代码快方式,当静态代码快执行的时候初始化该对象

    c. 枚举单例无法被破解的原因

    • 无参方式破解
    Class<?> classInfo = Class.forName("com.jarye.singleton.v6.SingleV6");
    Constructor<?> declaredConstructor = classInfo.getDeclaredConstructor();
    declaredConstructor.setAccessible(true);
    declaredConstructor.newInstance();
    

    image.png

    • 参照源码的有参方式破解
    Class<?> classInfo2 = Class.forName("com.jarye.singleton.v6.SingleV6");
    Constructor<?> declaredConstructor2 = classInfo2.getDeclaredConstructor(String.class,Integer.class);
    declaredConstructor2.setAccessible(true);
    declaredConstructor2.newInstance("zhangsan",3);
    

    image.png
    image.png

    相关文章链接:
    <<<23中常用设计模式总览
    <<<代理模式(Proxy Pattern)
    <<<装饰模式(Decorator Pattern)
    <<<观察者模式(Observer Pattern)
    <<<单例模式(Singleton Pattern)
    <<<责任链模式(Chain of Responsibility Pattern)
    <<<策略模式(Strategy Pattern)
    <<<模板方法模式(Template Pattern)
    <<<外观/门面模式(Facade Pattern)
    <<<建造者模式(Builder Pattern)
    <<<适配器模式(Adapter Pattern)
    <<<原型模式(Prototype Pattern)
    <<<工厂相关模式(Factory Pattern)

  • 相关阅读:
    Java命令行启动jar包更改默认端口以及配置文件的几种方式
    Windows下带配置文件的mysql命令行安装方法
    Windows下mysql主从搭建
    Windows下mysql集群搭建
    CAP原则(CAP定理)、BASE理论(精简)
    进程间通讯的7种方式
    Go Web 编程之 数据库
    Go 每日一库之 fsnotify
    Go 每日一库之 viper
    Go 每日一库之 go-ini
  • 原文地址:https://www.cnblogs.com/jarye/p/14057443.html
Copyright © 2011-2022 走看看