Java设计模式001 --- 单例模式

2021-01-20 11:15

阅读:707

标签:设置   根据   win   方法   inf   破坏   let   类加载   odex   

前言

什么是单例模式?就是在一个应用程序中,一个类的实例有且仅有一个;这个类负责创建该类的实例;

一般来说单例是有状态的对象,比如全局设置、数据库dao实例、全局资源等,并且可以根据需求延迟加载或者即时加载;

即时加载单例模式

1、静态域单例(我不习惯别人说的饿汉、懒汉)

public class Singleton1 {
    private static Singleton1 instance = new Singleton1();

    // 是有构造器, 防止被实例化
    private Singleton1() {
    }

    public static Singleton1 getInstance() {
        return instance;
    }

    public void doWhatever() {
    }
}

使用方法:SingleTon1.getInstance()

特点:在类加载的时候就初始化好了,无线程安全问题;

           即时加载,但是存在单例被破坏的风险,如使用反射、序列化

反射方式:

public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    Class clazz = Singleton1.class;
    Constructor constructor = clazz.getDeclaredConstructor();
    constructor.setAccessible(true);
    Singleton1 singleton1 = constructor.newInstance();
    System.out.println(singleton1 == Singleton1.getInstance());
}

技术图片

序列化方式(前提是单例类实现了Serializale):

public static void main(String[] args) {
    Singleton1 singleton = Singleton1.getInstance();
    File file = new File("src\\main\\resources\\singleton.txt");
    try (FileOutputStream fos = new FileOutputStream(file);
         ObjectOutputStream oos = new ObjectOutputStream(fos)) {
        oos.writeObject(singleton);
    } catch (IOException ex) {
        ex.printStackTrace();
    }

    try (FileInputStream fis = new FileInputStream(file);
         ObjectInputStream ois = new ObjectInputStream(fis)) {
        Singleton1 singleton1 = (Singleton1) ois.readObject();
        System.out.println(singleton == singleton1);
    } catch (IOException | ClassNotFoundException e) {
        e.printStackTrace();
    }
}

技术图片

 2、上述问题的解决方案

为了解决反射对单例造成的破坏,可以做如下修改:在私有构造方法中判断实例是否为null,否则抛运行时异常

// 是有构造器, 防止被实例化
private Singleton1() {
    if (instance != null) {
        throw new RuntimeException();
    }
}

技术图片

上述方法对序列化不起作用,这也从侧面验证了反序列化创建的对象不依赖类的构造器,而是由JVM创建的;

为了解决序列化对单例造成的破坏,可做如下修改:在序列化类中添加私有readResolve方法

private Object readResolve() {
    return instance;
}

技术图片

虽然有以上方法可以解决上述问题,但是有更简便的单例模式 --- 枚举单例

3、枚举单例模式

枚举单例有如下几个有点:线程安全、能够防止反射和序列化带来的破坏、实现简单

public enum SingletonEnum {
    INSTANCE;

    public void doWhatever() {
        System.out.println("Single Enum.");
    }
}

使用方法:SingletonEnum.INSTANCE.doWhatever()

验证下反射和序列化场景是否会破坏单例:

public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    SingletonEnum instance = SingletonEnum.INSTANCE;
    // 验证两次获取的是否是同一个对象
    System.out.println(instance == SingletonEnum.INSTANCE);
    // 序列化场景
    File file = new File("src\\main\\resources\\singleton.txt");
    try (FileOutputStream fos = new FileOutputStream(file);
         ObjectOutputStream oos = new ObjectOutputStream(fos)) {
        oos.writeObject(instance);
    } catch (IOException ex) {
        ex.printStackTrace();
    }

    try (FileInputStream fis = new FileInputStream(file);
         ObjectInputStream ois = new ObjectInputStream(fis)) {
        SingletonEnum instance2 = (SingletonEnum) ois.readObject();
        System.out.println(instance == instance2);
    } catch (IOException | ClassNotFoundException e) {
        e.printStackTrace();
    }

    // 反射场景
    Class clazz = SingletonEnum.class;
    Constructor constructor = clazz.getDeclaredConstructor(String.class, int.class);
    constructor.setAccessible(true);
    SingletonEnum instance1 = constructor.newInstance();
    System.out.println(instance == instance1);
}

运行结果:

技术图片

 为啥使用反射的时候会抛异常呢?这是因为反射的源码是这样写的:

 技术图片

 那么又为啥枚举单例能够保证单例不被序列化破坏呢?

 这是因为Java中规定,每个枚举变量在JVM中都是唯一的,并且Java还规定,枚举类在反序列化时使用枚举类的valueOf方法

 枚举类反编译结果如下:

 技术图片

 线程安全又是为什么?从反编译结果就能看出,INSTANCE是静态的,并且初始化时就创建了实例

未完待续。。。

 

Java设计模式001 --- 单例模式

标签:设置   根据   win   方法   inf   破坏   let   类加载   odex   

原文地址:https://www.cnblogs.com/sniffs/p/12901754.html


评论


亲,登录后才可以留言!