破坏单例的两种方式

深入单例

破坏单例的两种方式

1,反射破坏单列

1.1 反射破坏单例模式

单例模式:只能有一个实例化对象,构造方法私有,提供一个全局访问的点

先来一个单例模式(懒汉)

/**
 * @Author : FuYu
 * @Despriotion : 懒汉模式
 */
public class LazySingleton {

    private static LazySingleton singleton = null;

    //构造方法私有
    private LazySingleton()
    {

    }
    
    //全局访问的点
    public static LazySingleton getInstance()
    {
        if (singleton == null)
        {
            singleton = new LazySingleton();
        }
        return singleton;
    }
}

正常情况下,一个类只有这一个实例,一般都是通过调用LazySingleton.getInstance()方法来调用拿到这个对象的实例,但是还有一种方法,那就是通过反射得到类对象

 /**
     * 反射破坏单例模式
     */
    @Test
    public void toEquals() throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        Class<?> c1 = LazySingleton.class;
        Constructor<?> c = c1.getDeclaredConstructor(null);
        //将此对象的accessible标志设置为指示的布尔值。 true的值表示反射对象应该在使用时抑制Java语言访问检查。 false的值表示反映的对象应该强制执行Java语言访问检查。
        c.setAccessible(true);
        //反射得到类实例对象
        LazySingleton n1 = (LazySingleton) c.newInstance();
        //方法得到类实例对象
        LazySingleton n2 = LazySingleton.getInstance();
        System.out.println(n1 == n2);
    }

result:false 两个不是同一个对象,额,反射底层还没研究,不过正常人都知道反射会生成一个新的对象(反射去调用类的构造方法,就是定义私有的构造也能)

1.2 怎么避免反射破坏单例模式

解决很简单,主要有两种方式,一种是通过构造方法修改,还有就是用枚举(推荐第二种)

1.2.1 构造方法修改

先定义一个全局变量

private static boolean flag = false

再修改构造方法

private LazySingleton()
    {
        synchronized (LazySingleton.class)
        {
            if (flag == false)
            {
                System.out.println("第一次实例化");
                //将变量改成true,表示他被实例化过
                flag = !flag;
            }
            else
            {
                throw new RuntimeException("单例模式被侵犯!");
            }
        }
    }

再次执行 结果后,第二次访问构造肯定是抛出异常了

result:

第一次实例化

java.lang.RuntimeException: 单例模式被侵犯!
...................
1.2.2 使用枚举单例
/**
 * @Author : FuYu
 * @Despriotion : 枚举类型单例
 */
public enum LazyEnumSingleton implements Serializable {
    INSTANCE;

    private static final long serialVersionUID = 1L;

    private LazyEnumSingleton() {}

    public static LazyEnumSingleton getInstance()
    {
        return INSTANCE;
    }
}

再次执行 结果后就报异常了

result:

java.lang.NoSuchMethodException: serializable.LazyEnumSingleton.<init>()
......................

2,序列化破坏单例

序列化底层也是用运用到了java反射,so,他也会破坏单例模式

2.1序列化破坏单例模式

/**
     * 序列化破坏单例
     */
    @Test
    public void toSerEquals() throws IOException, ClassNotFoundException {
        //实例对象
        LazySingletonS n1 = LazySingletonS.getInstance();
        //序列化
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D://obj"));
        objectOutputStream.writeObject(n1);
        //反序列化
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D://obj"));
        LazySingletonS n2 = (LazySingletonS) objectInputStream.readObject();
        //对比前后序列化是否是同一个对象
        System.out.println(n1 == n2);
    }

打印:false ,所有序列化也会破坏单例,学学网上查看源码

https://blog.csdn.net/wenbin516/article/details/85739015

2.2 怎么避免序列化破坏单例模式

避免序列化破环单列有两种方式,推荐第二种

2.1.1 被序列化的类实现readResolve()方法

在要序列化的类种实现一个方法就是了readResolve()

private Object readResolve(){
	return singletonS;
}

然后执行,打印:true ,所以这样能避免破话序列化

2.1.2 使用枚举单例

还有一种就是用枚举 Effective Java 书种写道:对应实例控制,枚举类型优先于readResolve 枚举确保了永远只创建了一个实例

/**
 * @Author : FuYu
 * @Despriotion : 枚举类型单例 实现序列化
 */
public enum LazyEnumSerSingleton  {
    INSTANCE;
}
/**
     * 枚举防止序列化破坏单例
     */
    @Test
    public void toEnumSerEquals() throws IOException, ClassNotFoundException {
        //实例对象
        LazyEnumSerSingleton n1 = LazyEnumSerSingleton.INSTANCE;
        //序列化
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D://obj"));
        objectOutputStream.writeObject(n1);
        //反序列化
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D://obj"));
        LazyEnumSerSingleton n2 = (LazyEnumSerSingleton) objectInputStream.readObject();
        //对比前后序列化是否是同一个对象
        System.out.println(n1 == n2);
    }

打印输出:true

Effective Java 中介绍有点多,还要仔细看看,总感觉浮在水面上啊!

参考资料:https://blog.csdn.net/wenbin516/article/details/85739015

好像还有第三种 clone也会破坏单列

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值