单例模式(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())

最强单例模式--枚举

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("\u6211\u662F\u65E0\u53C2\u6784\u9020\u51FD\u6570\u88AB\u6267\u884C\u5230\u4E86");
    }

    public void add()
    {
        System.out.println("\u6DFB\u52A0\u64CD\u4F5C\u88AB\u542F\u52A8");
    }

    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)

posted @ 2020-11-29 19:19  架构师_迦叶  阅读(91)  评论(0编辑  收藏  举报