博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
你的单例真的写对了吗?能经受住反射的考验吗
阅读量:4079 次
发布时间:2019-05-25

本文共 3027 字,大约阅读时间需要 10 分钟。

在我们日常的用法以及面试过程中,说到写单例,绝大部分都是采用二段锁写的,代码如下:

public class Singleton {
private volatile static Singleton singleton; /** * 私有构造器 */ private Singleton() { } public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; }}

下面来看一段调用:

public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {        Singleton singleton = Singleton.getInstance();        System.out.println(singleton);        Singleton reflectSingleton = (Singleton) Class.forName("org.white.whiteroot.Singleton").newInstance();        System.out.println(reflectSingleton);        System.out.println(singleton.equals(reflectSingleton));  }

运行结果如下:

org.white.whiteroot.Singleton@4a574795org.white.whiteroot.Singleton@f6f4d33false

可以看到我们常自以为没问题的单例,很容易就被反射攻破了。

那么解决方案如何呢,我们都知道,反射其实还是会调用私有的无参数构造器,那么我们做如下改动如何(很多网上的结论都是下面这种方式):

// 新增属性private static boolean constructed;// 改写私有构造方法private Singleton() {        if (constructed) {            throw new RuntimeException("Singleton has been constructed");        }        constructed = true;}

此时再调用上面的测试方法,执行结果如下:

org.white.whiteroot.Singleton@4a574795Exception in thread "main" java.lang.RuntimeException: Singleton has been constructed

可以看到通过反射构造失败了。

不过既然我们在用反射进行破坏,那么上面代码能经受住反射的考验吗?此处敲黑板。我们修改测试方法如下:

public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {        Singleton singleton = Singleton.getInstance();        System.out.println(singleton);        Class reflectSingletonClass = Class.forName("org.white.whiteroot.Singleton");        Field field = reflectSingletonClass.getDeclaredField("constructed");        field.setAccessible(true);        field.set(singleton, false);        Singleton reflectSingleton = (Singleton) reflectSingletonClass.newInstance();        System.out.println(reflectSingleton);        System.out.println(singleton.equals(reflectSingleton));  }

再次运行,结果如下:

org.white.whiteroot.Singleton@4a574795org.white.whiteroot.Singleton@23fc625efalse

我们发现,我们的单例再次被破坏了!

在我们日常编码中,很多事情都要动手去做,凡事多想一步。网上很多方案中都没有去尝试通过反射修改属性,这其实是一种不负责任的做法。
另外在overstackflow上有一种取巧的做法,不过也会存在反射的问题,其做法如下:

private Singleton() {        if (singleton != null) {            throw new RuntimeException("Singleton has been constructed");        }    }

这种做法依旧会存在一个问题,这个问题其实在以上所有方法中都存在:

Class reflectSingletonClass = Class.forName("org.white.whiteroot.Singleton");Field field = reflectSingletonClass.getDeclaredField("singleton");field.setAccessible(true);field.set(singleton, null);

通过如上方法在反射中讲singleton的值置为空,这样相当于完全破换掉单例,每次置空后通过调用getInstance方法都会生成新的对象

那么有没有不会被破坏的方式呢?答案当然是有的,从如下两个方案:

  1. 既然破坏单例都是通过反射破坏属性后调用无参构造器,那么我们可以把属性放到第三方去托管,比如把singleton放到redis托管,每次调用序列化回来,这样除非去攻击托管的地方,否则是改变不了咱们的单例的。
  2. 反射必定还是会调用构造器,那么咱们可以通过不调用构造器的方式来做单例对象,那就是枚举。

总结一句:目前来讲,通过枚举来防止反射是最优解决方案。

转载地址:http://drsni.baihongyu.com/

你可能感兴趣的文章
Mac环境下svn的使用
查看>>
github简单使用教程
查看>>
如何高效利用GitHub
查看>>
环境分支-git版本管理
查看>>
Spring AOP + Redis + 注解实现redis 分布式锁
查看>>
支付宝生活号服务号 用户信息获取 oauth2 登录对接 springboot java
查看>>
CodeForces #196(Div. 2) 337D Book of Evil (树形dp)
查看>>
uva 12260 - Free Goodies (dp,贪心 | 好题)
查看>>
uva-1427 Parade (单调队列优化dp)
查看>>
【设计模式】学习笔记14:状态模式(State)
查看>>
poj 1976 A Mini Locomotive (dp 二维01背包)
查看>>
斯坦福大学机器学习——因子分析(Factor analysis)
查看>>
linux对于没有写权限的文件如何保存退出vim
查看>>
IntelliJ IDEA 下的svn配置及使用的非常详细的图文总结
查看>>
【IntelliJ IDEA】idea导入项目只显示项目中的文件,不显示项目结构
查看>>
ssh 如何方便的切换到其他节点??
查看>>
JSP中文乱码总结
查看>>
Java实现DES加密解密
查看>>
HTML基础
查看>>
Java IO
查看>>