单列模式的七种创建方式 及 使用反射和序列化破解单列

一、描述

1、单例基本概念

在当前Jvm中只会有一个该实例对象

2、单例应用场景

1.项目中定义的配置文件
2.Servlet对象默认就是单例
3.线程池、数据库连接池
4.Spring中Bean对象默认就是单例
5.实现网站计数器
6.Jvm内置缓存框架(定义单例HashMap)
7.定义枚举常量信息

3/单例优缺点

优点:能够节约当前堆内存,不需要频繁New对象,能够快速访问。
缺点:当多个线程访问同一个单例对象的时候可能会存在线程安全问题。

二、单列模式的 7种创建方式

  • 如果加上 反射创建 + 序列化创建 , 则为 9 种创建方式(破解单列中重新创建)
  • 如果在加上 构造函数创建 , 则为 10 种 创建方式
  • 构造函数使用private 修饰 主要目的为 防止外部直接使用 new 对象初始化改类,变成多列

1、懒汉式(并发时- 存在安全缺陷)

package com.xijia;

import java.io.Serializable;

/**
 * 单例模式(懒汉式)
 * @author wangsong
 * @mail [email protected]
 * @date 2020/9/5 0005 9:49 
 * @version 1.0.0
 */
public class Singleton01 implements Serializable {

    private static Singleton01 singleton01 = null;

    /**
     * 私有化,禁止new 改对象
     */
    private Singleton01() {
    }

    /**
     * 此创建方法  缺陷: 如果两个线程同时进入该 getInstance()方法,会破坏数据初始化的数据
     * 如果: singleton01为计数器或火车票
     * ----a执行 初始化 singleton01 = 1
     * ----b执行 初始化 singleton01 = 1
     * 实际结果应该为2,但实际结果为1 ,有兴趣可自行模拟
     * @return
     */
    public static Singleton01 getInstance() {
        if (singleton01 == null) {
            singleton01 = new Singleton01();
        }
        return singleton01;
    }

    public static void main(String[] args) {
        // 初始化
        Singleton01 instance1 = Singleton01.getInstance();
        // 直接获取,不初始化, 但是遇到instance1和instance2 的调用同时进入if 条件,部分业务数据将不正确(如计数器,火车票等)
        Singleton01 instance2 = Singleton01.getInstance();
        System.out.println(instance1 == instance2);
    }

}

2、懒汉式 + synchronized 锁机制(不存在安全缺陷 , 但性能低)

package com.xijia;

/**
 * 单列模式(懒汉式 + synchronized 锁机制)
 * @author wangsong
 * @mail [email protected]
 * @date 2020/9/5 0005 9:58
 * @version 1.0.0
 */
public class Singleton02 {

    private static Singleton02 singleton02;

    private Singleton02() {
    }

    /**
     * 初始化 singleton02, 改方式避免了案例一 计数器重复初始化导致结果错误的问题,但存在一定的缺陷
     * 缺陷:
     * 初始化singleton02 无缺陷
     * 但 获取singleton02 数据时出现缺陷, 当我们获取singleton02时,两个线程同时获取singleton02时,必须排队获取singleton02,这显然是会严重影响性能的
     * @return
     */
    public static synchronized Singleton02 getInstance() {
        if (singleton02 == null) {
            singleton02 = new Singleton02();
        }
        return singleton02;
    }

    public static void main(String[] args) {
        // 初始化singleton02
        Singleton02 instance1 = Singleton02.getInstance();
        // 直接获取,不初始化
        Singleton02 instance2 = Singleton02.getInstance();
        System.out.println(instance1 == instance2);
    }
}

3、懒汉式 + 双重检验锁(无缺陷 + 性能最优)

package com.xijia;


/**
  * 单列模式(懒汉式 + 双重检验锁)
  * @author wangsong
  * @mail  [email protected]
  * @date  2020/9/5 0005 10:06
  * @version 1.0.0
  */
public class Singleton03 {
    private static Singleton03 singleton03;

    private Singleton03() {
    }

    /**
     * 双重检验锁初始化 singleton03
     * 无缺陷
     * ----  修复案例2的 获取数据需要排队的问题,获取数据直接获取,
     * ----- 如果遇到多个线程同时进入 if ,加锁排队,第一个进入的线程初始化了对象,下一个排队的人进入后判断发现对象已经初始化了,就不重新初始化了
     * @return
     */`在这里插入代码片`
    public static Singleton03 getInstance() {
        // 上锁(创建该对象) 第一次判断
        if (singleton03 == null) {
            synchronized (Singleton03.class) {
                //第二次判断
                if (singleton03 == null) {
                    singleton03 = new Singleton03();
                }
            }
        }
        return singleton03;
    }


    public static void main(String[] args) {
        Singleton03 instance1= Singleton03.getInstance();
        Singleton03 instance2= Singleton03.getInstance();
        System.out.println(instance1==instance2);
    }
}

4、 饿汉式 (常用与静态变量, 缺点未使用占用内存资源)

package com.xijia;


/**
 * 饿汉式
 * @author wangsong
 * @mail [email protected]
 * @date 2020/9/5 0005 10:12
 * @version 1.0.0
 */
public class Singleton04 {

    /**
     * 当class文件被加载的时候就创建该对象,常用于创建常量
     */
    public static final Singleton04 singleton04 = new Singleton04();

    private Singleton04() {
    }

    public static void main(String[] args) {
        System.out.println(Singleton04.singleton04 == Singleton04.singleton04);
    }

}

5、静态方法块创建 (不常用)

package com.xijia;


/**
 * 静态方法区
 * @author wangsong
 * @mail [email protected]
 * @date 2020/9/5 0005 10:14
 * @version 1.0.0
 */
public class Singleton05 {


    private static Singleton05 singleton05 = null;

    private Singleton05() {
    }

    /**
     * 只会初始化一次(或常用的 init方法)
     */
    static {
        System.out.println("当前class被加载");
        singleton05 = new Singleton05();
    }

    public static synchronized Singleton05 getInstance() {
        return Singleton05.singleton05;
    }


    public static void main(String[] args) {
        System.out.println(Singleton05.getInstance() == Singleton05.getInstance());
    }
}

6、静态内部类创建(安卓常用)

package com.xijia;


/**
  * 静态内部类
  * @author wangsong
  * @mail  [email protected]
  * @date  2020/9/5 0005 10:16
  * @version 1.0.0
  */
public class Singleton06 {

    public Singleton06() {
    }

    /**
     *  静态内部类 (安卓常用)
     */
    private static class SingletonHolder {
        private static final Singleton06 singleton06 = new Singleton06();
    }

    /**
     * 获取静态内部类的 singleton06
     * @return
     */
    public static Singleton06 getInstance() {
        return SingletonHolder.singleton06;
    }


    public static void main(String[] args) {
        Singleton06 instance1 = Singleton06.getInstance();
        Singleton06 instance2 = Singleton06.getInstance();
        System.out.println(instance1 == instance2);
    }
}

7、枚举方式创建(绝对的线程安全)

package com.xijia;


/**
 * 枚举方式创建
 * @author wangsong
 * @date 2020/9/5 0005 10:21
 * @return
 * @version 1.0.0
 */
public enum Singleton07 {
    INSTANCE;

    public void addUser() {
        System.out.println("我是枚举,我先天性安全");
    }


    Singleton07() {
        System.out.println(">>>Singleton07无参构造函数执行<<");
    }


    public static void main(String[] args) {
        // 只会执行一次构造函数
        System.out.println(Singleton07.INSTANCE == Singleton07.INSTANCE);
    }
}

三、破解单列

1、反射破解

public class Test001 {
    public static void main(String[] args) throws Exception {

        // 使用反射机制创建我们的对象
        Class<?> aClass = Class.forName("com.xijia.Singleton01");
        //  getDeclaredConstructor();获取当前类(不包含父类),getConstructor 所有的  包含父类构造函数
        Constructor<?> constructor = aClass.getDeclaredConstructor();
        constructor.setAccessible(true);
        // 走无参构造函数 反射创建对象成功
        Singleton01 instance1 = (Singleton01) constructor.newInstance();
        Singleton01 instance2 = Singleton01.getInstance();
        /**
         * 将输出false, 单列对象 Singleton01 被重复创建, singleton01 在静态区的值被重新初始化,原数据将被破坏
         */
        System.out.println(instance1 == instance2);
    }
}

在这里插入图片描述

2、序列化破解

/**
 * 序列化破解单列 (当类添加了 implements Serializable 的,都将可以破解单列)
 * @author wangsong
 * @date 2020/9/5 0005 10:26
 * @return
 * @version 1.0.0
 */
public class Test002 {
    public static void main(String[] args) throws Exception {
        // 1.需要将该对象序列化到本地存放
        FileOutputStream fos =  new FileOutputStream("d:/code/user.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        Singleton01 instance1 = Singleton01.getInstance();
        oos.writeObject(instance1);
        oos.close();
        fos.close();
        //2.从硬盘中反序列化对象到内存中
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/code/user.txt"));
        Singleton01 instance2 = (Singleton01) ois.readObject();
        /**
         * 将输出false, 单列对象 Singleton01 被重复创建, singleton01 在静态区的值被重新初始化,原数据将被破坏
         */
        System.out.println(instance1==instance2);

    }
}

在这里插入图片描述

3、尝试反射破解枚举(无法破解)

/**
  * 方式破解单列(无法破解,直接抛出异常)
  * @author wangsong
  * @mail  [email protected]
  * @date  2020/9/5 0005 10:33
  * @version 1.0.0
  */
public class Test004 {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {

        Class<?> aClass = Class.forName("com.xijia.Singleton07");
        /**
         * 直接抛出,Exception in thread "main" java.lang.InstantiationException: com.xijia.Singleton07 异常
         */
        Object o = aClass.newInstance();
    }
}

在这里插入图片描述

4、尝试序列化破解枚举(无法破解)


/**
 * 序列化破解枚举(先天性安全,无法破解)
 * @author wangsong
 * @date 2020/9/5 0005 10:30
 * @return
 * @version 1.0.0
 */
public class Test003 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        // 1.将对象序列化存入到本地文件中
        FileOutputStream fos = new FileOutputStream("d:/code/a.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        Singleton07 instance1 = Singleton07.INSTANCE;
        oos.writeObject(Singleton07.INSTANCE);
        oos.close();
        fos.close();

        //2.从硬盘中反序列化对象到内存中
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/code/a.txt"));
        Singleton07 instance2 = (Singleton07) ois.readObject();
        /**
         * 输出true, 枚举类没有被重新创建,原数据绝对安全
         */
        System.out.println(instance1 == instance2);
    }
}

在这里插入图片描述

  • 以上部分内容来自于蚂蚁课堂 http://www.mayikt.com/

  • 个人开源项目(通用后台管理系统)–> https://gitee.com/wslxm/spring-boot-plus2 , 喜欢的可以看看

  • 本文到此结束,如果觉得有用,动动小手点赞或关注一下呗,将不定时持续更新更多的内容…,感谢大家的观看!

猜你喜欢

转载自blog.csdn.net/qq_41463655/article/details/108416482