创建者模式 -- 单例模式(反射&序列化)

单例模式详解
本文深入探讨了五种实现单例模式的方法:饿汉式、懒汉式、双重锁检测、静态内部类和枚举,对比了它们的优缺点,并讲解了如何防止反射和反序列化对单例模式的破坏。

看的视频:https://www.bilibili.com/video/av43896218/?p=286

实现方式:(构造器私有,提供一个外部可以访问的方法提供实例

1、饿汉式:线程安全,调用效率高, 不能延时加载

2、懒汉式:线程安全,调用效率不高,可以延时加载(要用的时候才加载  )

3、双重锁检测式:“由于JVM底层内部模型原因,偶尔会出现问题,不建议使用”

4、静态内部类式:线程安全,调用效率高,且可以实现延时加载

(上面的四种方式都可以通过反射和反序列化 破坏 单例模式)

5、枚举:线程安全,调用效率高,不能延时加载,可以天然的防止反射和反序列化

如果单例对象占用资源少,不需要延时加载:

枚举式 优于 饿汉式

如果单例对象占用资源大, 需要延时加载:

静态内部类式 优于 懒汉式 

 

饿汉式:

/*
饿汉式: 最开始就已经吧实例给创建好了
 */
public class Singleton01 {
    public static Singleton01 instance = new Singleton01();  //一开始就已经创建好了
    private Singleton01(){
    }
    public static Singleton01 getInstance(){
        return instance;
    }
}

懒汉式:

/*
懒汉式: 用的时候才会初始化实例   效率不高就是因为有synchronized这个关键字
 */
public class Singleton02 {
    private static Singleton02 instance = null;
    private Singleton02(){
    }
    public static synchronized Singleton02 getInstance(){
        if(instance == null){
            instance = new Singleton02();
        }
        return instance;
    }
}

静态内部类:

/*
静态内部类: 只有在主动调用这个类的时候才会加载   外部类加载的时候 静态内部类不会加载
 */
public class Singleton03 {
    static class Inner{
        private static Singleton03 instance = new Singleton03();
    }
    private Singleton03(){

    }
    public static Singleton03 getInstance(){
        return Inner.instance;
    }
}

枚举:

/*
枚举  天然的可以防止 反射和反序列化
 */
public enum Singleton04 {
    INSTANCE; //就表示一个实例 获取的时候直接  Singleton04.INSTANCE 
    public void method(){
        //这里可以写一些方法
    }
}

 

通过反射破坏单例模式:

public class Test01 {
    public static void main(String[] args) throws Exception{
            Class<?> clazz = Class.forName("com.test.Singleton01");
            //获取构造函数:
            Constructor c = clazz.getDeclaredConstructor();
            c.setAccessible(true); //对于私有属性和方法 设置为允许访问
            //通过构造函数 构造实例
            Singleton01 singleton001 = (Singleton01)c.newInstance();
            Singleton01 singleton002 = (Singleton01)c.newInstance();
            System.out.println(singleton001 == singleton002);  // false
    }
}

如何破坏反射:

public class Singleton01 {
    private static boolean  flag = false; //标志位
    public static Singleton01 instance = new Singleton01();  //一开始就已经创建好了
    // 上面的已经调用过一次 这个私有的构造函数了

private Singleton01(){ //反射的时候操作的就是这个私有的构造函数 synchronized (Singleton01.class){ if(flag == false){ flag = true; }else{ //表示第二次用构造函数进行实例化 throw new RuntimeException("单例模式正在被破坏"); } } } public Singleton01 getInstance(){ return instance; } }

在用反射就会报错....

 

通过反序列化破坏单例模式:

 对象序列化:将对象状态转换为字节流的过程,可以将其保存到磁盘文件中或者通过网络发送到任何其他程序中。

 反序列化:将字节流转化为对象的过程。

public class Test01 {
    public static void main(String[] args) throws Exception{
//序列化 singleton001 Singleton01要实现 Serializable Singleton01 singleton001 = Singleton01.getInstance(); Singleton01 singleton002 = Singleton01.getInstance(); FileOutputStream fos = new FileOutputStream("d:/a.txt"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(singleton001); oos.close(); fos.close(); //反序列化 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/a.txt")); Singleton01 singleton003 = (Singleton01)ois.readObject(); System.out.println(singleton001 == singleton002); //true System.out.println(singleton001 == singleton003); //false } }

防止反序列化:

public class Singleton01 implements Serializable {
    public static Singleton01 instance = new Singleton01();  //一开始就已经创建好了
    private Singleton01(){
    }
    public static Singleton01 getInstance(){
        return instance;
    }
    //在反序列化的时候  直接调用这个方法 返回instance   而不是返回反序列化后的新对象
    private Object readResolve() throws ObjectStreamException {
        return instance;
    }
}

 

转载于:https://www.cnblogs.com/DDiamondd/p/10966745.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值