package instance.lazy; import java.io.ObjectStreamException; import java.io.Serializable; /** * 懒汉式单例 * @author jingzi * 单例模式是最常用的设计模式,一个完美的单例需要做到哪些事呢? * 1、单例 * 2、延迟加载 * 3、线程安全 * 4、没有性能问题 * 5、防止序列化产生新对象 * 6、防止反射攻击 */ public class LazyType implements Serializable { private static final long serialVersionUID = 6427931149119200875L; /** * 声明为volatile,保证多线程可见性、防止“instance = new LazyType()”指令重排序。 * 注:instance = new LazyType()是非原子性操作。 * instance = null:延迟加载。 */ private volatile static LazyType instance = null; private static boolean flag = false; private LazyType() { //防止反射攻击 synchronized (LazyType.class) { if (false == flag) { flag = !flag; } else { //被反射攻击,抛出异常。 throw new RuntimeException("Reflected attack!"); } } } /** * 执行两次校验:很有必要,这样可以保证线程安全。 * 当多线程调用时,如果多个线程同时执行完了第一次检查,其中一个进入他不代码块创建了实例, * 后面的线程因第二次检查不会创建实例。 * @return */ public static LazyType getInstance() { if (null == instance) {//第一次检查 synchronized (LazyType.class) { if (null == instance) {//第二次检查 instance = new LazyType(); } } } return instance; } /** * 如果实现了Serializable, 必须重写这个方法,才能保证其反序列化依旧是单例,即防止序列化产生新对象。 * @return * @throws ObjectStreamException */ private Object readResolve() throws ObjectStreamException { return instance; } /** * 所执行的业务功能 */ public void doSomethingElse() { } }
测试,防止单例模式被java反射攻击:
package instance.lazy; import java.lang.reflect.Constructor; public class LazyTypeModifiedReflectAttack { public static void main(String[] args){ try { Class<LazyType> classType = LazyType.class; Constructor<LazyType> c = classType.getDeclaredConstructor(null); c.setAccessible(true); LazyType e1 = (LazyType)c.newInstance(); System.out.println("e1.hashCode():" + e1.hashCode()); LazyType e2 = LazyType.getInstance(); System.out.println("e2.hashCode():" + e2.hashCode()); } catch (Exception e) { e.printStackTrace(); } } }
测试结果:
e1.hashCode():118352462 java.lang.RuntimeException: Reflected attack! at instance.lazy.LazyType.<init>(LazyType.java:36) at instance.lazy.LazyType.getInstance(LazyType.java:50) at instance.lazy.LazyTypeModifiedReflectAttack.main(LazyTypeModifiedReflectAttack.java:16)
这么写的具体原因可见:https://www.zhihu.com/question/29971746
此外,如何防止单例模式被JAVA反射攻击: https://blog.csdn.net/u013256816/article/details/50525335。
参考内容:
https://blog.csdn.net/wzgiceman/article/details/51809985
https://blog.csdn.net/huangyuan_xuan/article/details/52193006