本文共 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方法都会生成新的对象。
那么有没有不会被破坏的方式呢?答案当然是有的,从如下两个方案:
总结一句:目前来讲,通过枚举来防止反射是最优解决方案。
转载地址:http://drsni.baihongyu.com/