设为首页 收藏本站
开启辅助访问 快捷导航
菜单
猿人部落 主页 资讯 查看内容

GOF23设计模式-单例模式-5种实现方式比较和防止反射与反序列化漏洞

2019-7-26 18:07 发布者: 唯你懂我心 评论 0 查看 907
计划模式GOF23-单例模式5种方式写法与比力计划模式计划模式的分类单例模式核心作用常见应用场景单例模式的长处常见的

计划模式GOF23-单例模式5种方式写法与比力

  • 计划模式
    • 计划模式的分类
    • 单例模式
      • 核心作用
      • 常见应用场景
      • 单例模式的长处
    • 常见的五种单例模式实现方式
      • 实现方式1:饿汉式(单例对象立即加载)
      • 实现方式2:懒汉式(单例对象延伸加载)
      • 实现方式3:双重检测锁(由于JVM内部模子偶尔出现标题,不发起利用)
      • 实现方式4:静态内部类(也是一种延伸加载方式)
      • 实现方式5:罗列类实现(实现简朴,无是延伸加载)
    • 怎样选用?
    • 常见五种单例模式在多线程情况下的服从测试
    • 通过反射破解以上单例模式及怎样防止(不包罗罗列式)
    • 通过反序列化破解以上单例模式及怎样防止(不包罗罗列式)

计划模式

学习计划模式,就是将计划者的头脑融入学习和工作中,更高条理的思考,而不是死记硬背模式代码。

计划模式的分类

GOF将计划模式分为以下三个模块:

  1. 创建型模式: 单例模式、工厂模式、抽象工厂模式、制作者模式、原型模式;
  2. 布局型模式: 适配器模式、桥接模式、组合模式、装饰模式、表面模式、享元模式、署理模式;
  3. 举动型模式: 模板方法模式、下令模式、迭代器模式、观察者模式、中介模式、备忘录模式、表明器模式、状态模式、战略模式、职责链模式、访问者模式;

单例模式

核心作用

包管一个类只有一个实例,而且提供一个访问该实例的全局访问点。

常见应用场景

  1. Windows的Task Manager(使命管理器)就是很范例的单例模式
  2. Windows的Recycle Bin(接纳站)也是很范例的单例模式,在整个体系运行过程中,接纳站不绝维护着仅有的一个实例。
  3. 网站的计数器,一样平常也接纳单例模式实现,否则难以同步。
  4. 操纵体系的文件体系,也是大的单例模式实现的详细例子,一个操纵体系只能有一个文件体系。
  5. 在servlet编程中,每个servlet 也是单例。
  6. Application也是单例的范例应用(Servlet编程中有涉及到)。
  7. 应用步调的日记应用,一样平常都接纳单例模式实现,这一样平常是由于共享的日记文件不绝处于打开状态,由于只能有一个实例去操纵,否则内容不好追加。
  8. 项目中读取设置文件的类,一样平常也只有一个对象。没须要每次利用设置文件信息数据,都去new一个新的对象去读取。
  9. 数据库毗连池的计划一样平常也是接纳单例模式。数据库毗连是一种数据库资源。
  10. 在Spring中,每个Bean默认就是单例的,如许做的长处是方便Spring容器的管理。
  11. 在Spring MVC框架/Struts1框架中,控制器对象也是单例。

单例模式的长处

  1. 由于单例模式只天生了一个实例,镌汰了体系性能开销,当一个对象的产生须要比力多的资源时,如读取设置文件、产生其他依靠对象时,则可以通过在应用启动时直接产生一个单例对象,然后永世驻留内存的方式来管理。
  2. 单例模式可以在体系设置全局的访问点,优化共享资源访问,比方可以计划一个单例类,负责全部数据表的映射处理处罚。

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

  • 告急:
    • 饿汉式(线程安全,调用服从高。但是不能延时加载。)
    • 懒汉式(线程安全,调用服从不高。但是可以延时加载。)
  • 其他:
    • 双重检测锁式(由于JVM底层内部模子缘故原由,偶尔出现标题。不发起利用。)
    • 静态内部类式(线程安全,调用服从高。可以延时加载。)
    • 罗列单例(线程安全,调用服从高,不能延时加载。)

实现方式1:饿汉式(单例对象立即加载)

实今世码

package com.tumbler.singleton;

/**
 * User:tumbler
 * Desc:单例模式:饿汉式
 */
public class SingletonDemo1 {
    //类初始化时立即加载此对象,天然线程安全,没有延时加载
    private static SingletonDemo1 instance = new SingletonDemo1();

    //构造方法私有化
    private SingletonDemo1(){}

    //提供外部访问实例方法,方法没有同步,调用服从高
    public static SingletonDemo1 getInstance(){
        return instance;
    }
}

测试

package com.tumbler.singleton;

/**
 * User:tumbler
 * Desc:测试单例模式
 */
public class Client {
    public static void main(String[] args){
        SingletonDemo1 instance1 = SingletonDemo1.getInstance();
        SingletonDemo1 instance2 = SingletonDemo1.getInstance();
        System.out.println(instance1 == instance2); // true
    }
}

分析

  • 饿汉式单例模式代码中,static变量会在类装载时初始化,此时也不会涉及多个线程对象访问该对象的标题。假造机包管只会装载一次该类,肯定不会发生并发访问的标题。因此可以省略synchronized关键字。
  • 标题:如果只是加载本类,而不是要调用getInstance(),以致永久也没有调用,则会造成资源浪费!
    UML
    在这里插入图片形貌

实现方式2:懒汉式(单例对象延伸加载)

代码实现

package com.tumbler.singleton;

/**
 * User:tumbler
 * Desc:单例模式:懒汉式
 */
public class SingletonDemo2 {
    //类初始化时不初始化此对象,延时加载
    private static SingletonDemo2 instance;

    //私有化构造器
    private SingletonDemo2() {}

    //方法同步,调用服从低
    public static synchronized SingletonDemo2 getInstance(){
        if (instance == null) {
            instance = new SingletonDemo2();
        }
        return instance;
    }
}

测试

package com.tumbler.singleton;

/**
 * User:tumbler
 * Desc:测试单例模式
 */
public class Client {
    public static void main(String[] args){
        SingletonDemo2 instance1 = SingletonDemo2.getInstance();
        SingletonDemo2 instance2 = SingletonDemo2.getInstance();
        System.out.println(instance1 == instance2); // true
    }
}

分析

  • 懒加载(延时加载),真正用的时间再去加载。
  • 标题:资源利用率高了,但是每次调用getInstance()方法都要同步,并发服从低了。
    UML
    在这里插入图片形貌

实现方式3:双重检测锁(由于JVM内部模子偶尔出现标题,不发起利用)

代码实现

package com.tumbler.singleton;

/**
 * User:tumbler
 * Desc:单例模式:双重校验锁
 */
public class SingletonDemo3 {
    private volatile static SingletonDemo3 instance;
    private SingletonDemo3() {}
    public static SingletonDemo3 getInstance(){
        if (instance == null) {
            synchronized (SingletonDemo3.class) {
                if (instance == null) {
                    instance = new SingletonDemo3();
                }
            }
        }
        return instance;
    }
}

测试

package com.tumbler.singleton;

/**
 * User:tumbler
 * Desc:测试单例模式
 */
public class Client {
    public static void main(String[] args){
        SingletonDemo3 instance1 = SingletonDemo3.getInstance();
        SingletonDemo3 instance2 = SingletonDemo3.getInstance();
        System.out.println(instance1 == instance2); // true
    }
}

分析

  • 此模式将同步内容下放到if内部,进步了实行服从,不必每次获取对象时都举行同步,只有第一次才同步,实例创建后就没须要了。
  • 标题:由于编译器优化缘故原由和JVM内部模子缘故原由,偶尔会出现标题,以是不发起利用。
  • instance 接纳 volatile 修饰是很有须要的,由于 instance = new SingletonDemo3() 这句话可以分为三步:
    1. 为 instance 分配内存空间;
    2. 初始化 instance ;
    3. 将 instance 指向分配的内存空间。
      但是由于JVM具有指令重排的特性,实行序次有大概变成 1-3-2。 指令重排在单线程下不会出现标题,但是在多线程下会导致一个线程得到一个未初始化的实例。比方:线程T1实行了1和3,此时T2调用 getInstance() 后发现 instance 不为空,因此返回 instance , 但是此时的 instance 还没有被初始化。
      利用 volatile 会克制JVM指令重排,从而包管在多线程下也能正常实行。
      UML
      在这里插入图片形貌

实现方式4:静态内部类(也是一种延伸加载方式)

代码

package com.tumbler.singleton;

/**
 * User:tumbler
 * Desc:单例模式:静态内部类
 */
public class SingletonDemo4 {
    private static class SingletonClassInstance {
        private static final SingletonDemo4 instance = new SingletonDemo4();
    }
    private SingletonDemo4() {}
    
    public static SingletonDemo4 getInstance() {
        return SingletonClassInstance.instance;
    }
}

测试

package com.tumbler.singleton;

/**
 * User:tumbler
 * Desc:测试单例模式
 */
public class Client {
    public static void main(String[] args){
        SingletonDemo4 instance1 = SingletonDemo4.getInstance();
        SingletonDemo4 instance2 = SingletonDemo4.getInstance();
        System.out.println(instance1 == instance2); // true
    }
}

分析

  • 外部类没有static属性,则不会像饿汉式那样立即加载对象。
  • 只有真正调用getInstance()方法时才会加载静态内部类。加载类时是线程安全的,instance是static final范例,包管了内存中只有如许一个实例存在,而且只能被赋值一次,从而包管了线程安全性。
  • 兼备了并发高效调用和延伸加载的上风。
    UML
    在这里插入图片形貌

实现方式5:罗列类实现(实现简朴,无是延伸加载)

代码

package com.tumbler.singleton;

/**
 * User:tumbler
 * Desc:单例模式:罗列实现
 */
public enum  SingletonDemo5 {
    //界说一个罗列元素,它就代表了一个SingletonDemo的对象
    INSTANCE;
    /**
     * 单例可以有本身的操纵
     */
    public void singletonOperation() {
        //功能处理处罚
    }
}

测试

package com.tumbler.singleton;

/**
 * User:tumbler
 * Desc:测试单例模式
 */
public class Client {
    public static void main(String[] args){
        SingletonDemo5 instance1 = SingletonDemo5.INSTANCE;
        SingletonDemo5 instance2 = SingletonDemo5.INSTANCE;
        System.out.println(instance1 == instance2); // true
    }
}

分析

  • 长处:实现简朴。罗列本身就是单例模式。由JVM从根本上提供了保障,制止通过反射和反序列化的毛病。
  • 缺点:无延伸加载。
    UML
    在这里插入图片形貌

怎样选用?

  • 单例对象,占用资源少、不需延伸加载:罗列式 好于 饿汉式;
  • 单例对象,占用资源大、须要延伸加载:静态内部类式 好于 懒汉式。

常见五种单例模式在多线程情况下的服从测试

利用CountDownLatch:同步辅助类,在完成一组正在其他线程中实行的操纵之前,它允许一个或多个线程不绝等候。

  • countDown():当火线程调用此方法,则计数减一(发起放到finally里实行)。
  • await():调用此方法会不绝壅闭当火线程,直到计时器的值为0。

下面是我测试的结果(10个线程调用1000000次),各人关注相对值即可,差别情况下的步调测试值完全不一样。

计划模式 时间(ms)
饿汉式 71
懒汉式 455
静态内部类式 77
双重校验锁式 83
罗列式 84

测试代码示例

package com.tumbler.singleton;

import java.util.concurrent.CountDownLatch;

/**
 * User:tumbler
 * Desc:测试比力五种方式单例模式的服从
 */
public class TestSingletonDemo {
    public static void main(String[] args) throws Exception{

        long start = System.currentTimeMillis();
        int threadNum = 10;
        final CountDownLatch countDownLatch = new CountDownLatch(threadNum);

        for (int i = 0; i < threadNum; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000000; j++) {
                    //Object o = SingletonDemo1.getInstance();
                    Object o = SingletonDemo5.INSTANCE;
                }
                countDownLatch.countDown();
            }).start();
        }
        countDownLatch.await();
        long end = System.currentTimeMillis();

        System.out.println("总耗时:" + (end - start));
    }
}

通过反射破解以上单例模式及怎样防止(不包罗罗列式)

测试代码

package com.tumbler.singleton;

import java.lang.reflect.Constructor;

/**
 * User:tumbler
 * Desc:测试反射粉碎单例模式,以懒汉式为例
 */
public class TestReflex {
    public static void main(String[] args) throws Exception {
        SingletonDemo2 instance1 = SingletonDemo2.getInstance();
        SingletonDemo2 instance2 = SingletonDemo2.getInstance();
        System.out.println(instance1 == instance2); // true  单例

        //通过反射粉碎单例模式
        Class clazz = (Class) Class.forName("com.tumbler.singleton.SingletonDemo2");
        Constructor constructor = clazz.getDeclaredConstructor(null);
        constructor.setAccessible(true); // 访问私有构造器
        SingletonDemo2 instance3 = constructor.newInstance();
        SingletonDemo2 instance4 = constructor.newInstance();
        System.out.println(instance3 == instance4); //false  单例被粉碎
    }
}

怎样防止?
在私有构造器内抛出非常,即多次构造直接报错制止。
修改SingletonDemo2:

package com.tumbler.singleton;

/**
 * User:tumbler
 * Desc:单例模式:懒汉式  防止反射粉碎单例
 */
public class SingletonDemo2 {
    //类初始化时不初始化此对象,延时加载
    private static SingletonDemo2 instance;

    //私有化构造器
    private SingletonDemo2() {
        if(instance != null) {
            throw new RuntimeException();
        }
    }

    //方法同步,调用服从低
    public static synchronized SingletonDemo2 getInstance(){
        if (instance == null) {
            instance = new SingletonDemo2();
        }
        return instance;
    }
}

通过反序列化破解以上单例模式及怎样防止(不包罗罗列式)

要是用序列化,则先给SingletonDemo2实现序列化接口

public class SingletonDemo2 implements Serializable 

测试代码

package com.tumbler.singleton;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

/**
 * User:tumbler
 * Desc:通过反序列化粉碎单例模式
 */
public class TestDeserialize {
    public static void main(String[] args) throws Exception{
        SingletonDemo2 instance1 = SingletonDemo2.getInstance();
        SingletonDemo2 instance2 = SingletonDemo2.getInstance();
        System.out.println(instance1 == instance2); // true  单例

        //序列化
        FileOutputStream fos = new FileOutputStream("D:/a.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(instance1);
        oos.close();
        fos.close();

        //反序列化
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:/a.txt"));
        SingletonDemo2 instance3 = (SingletonDemo2) ois.readObject();
        System.out.println(instance1 == instance3); // false 粉碎单例
    }
}

怎样防止
通过界说readResolve()方法制止粉碎。在SingletonDemo2 添加以下方法即可:

    //反序列化
    private Object readResolve() throws ObjectStreamException {
        return instance;
    }


路过

雷人

握手

鲜花

鸡蛋
收藏 邀请
上一篇:千万不要因为安逸而忘记努力下一篇:JAVA基础7-Stream基础学习笔记

相关阅读

一周热门

头条攻略!

日排行榜

相关分类