被破坏的单例模式
我们知道饿汉单例天生是线程安全的,但是通过其他途径单例模式是可以被破坏的。懒汉亦如此。
1、通过反射来破解单例模式
public class Eagersingleton implements Serializable {
private static final long serialVersionUID = 888L;
private static Eagersingleton m_instance = new Eagersingleton();// 初始化时已经自行实例化
// 私有默认构造方法
private Eagersingleton() {
}
// 静态工厂方法
public static Eagersingleton getInstance() {
return m_instance;
}
}
public class Test1 {
public static void main(String[] args) throws NoSuchMethodException,
SecurityException {
// 获取单例对象
Eagersingleton instance = Eagersingleton.getInstance();
Eagersingleton instance2 = null;
try {
Class cls = Eagersingleton.class;
Constructor constructor = cls.getDeclaredConstructor(null);
// 关掉安全检查,可以调用私有构造器
constructor.setAccessible(true);
// 通过反射得到一个对象
instance2 = (Eagersingleton) constructor.newInstance(null);
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(instance);
System.out.println(instance2);
}
}
代码可以简单归纳为三个步骤:
-
第一步,获得单例类的构造器。
-
第二步,把构造器设置为可访问。
-
第三步,使用newInstance方法构造对象。
打印出来的结果不一样,说明这两个对象就是不同的对象,这样就破解了单例模式。
解决方案:1.1、避免反射
反射是通过它的Class对象来调用构造器创建出新的对象,我们只需要在构造器中手动抛出异常,导致程序停止就可以达到目的了。
![](https://img-blog.csdn.net/20180415134309358)
1.2、用枚举类实现单例模式(最常用)
public enum SingletonEnum {
m_instance;
}
注:
-
防止反射,线程安全。
-
非使用懒加载,其单例对象在枚举类呗加载的时候就被初始化了。
2、通过序列化和反序列化破解单例
public class Test2 {
public static void main(String[] args) throws FileNotFoundException,
ClassNotFoundException {
// 获取单例对象
Eagersingleton instance = Eagersingleton.getInstance();
// 通过反序列化读取对象
Eagersingleton instance2 = null;
try {
ObjectOutputStream oossStream = new ObjectOutputStream(
new FileOutputStream("D:/EagersingletonTest.txt"));
// 通过序列化把对象写到文件中
oossStream.writeObject(instance);
oossStream.flush();
oossStream.close();
// 读取文件的对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
"D:/EagersingletonTest.txt"));
instance2 = (Eagersingleton) ois.readObject();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(instance);
System.out.println(instance2);
}
}
打印出来的结果不一样,说明这两个对象就是不同的对象,破解了单例模式。
解决方案:2.1、避免序列化
![](https://img-blog.csdn.net/20180415134652623)
readResolve()这个方法是基于回调的,反序列化时,如果定义了readResolve()则直接返回此方法指定的对象,而不需要在创建新的对象。
![](https://img-blog.csdn.net/20180415134815296)
3、小纪
-
1. volatile关键字不但可以防止指令重排,也可以保证线程访问的变量值是主内存中的最新值。
-
2.使用枚举实现的单例模式,不但可以防止利用反射强行构建单例对象,而且可以在枚举类对象被反序列化的时候,保证反序列的返回结果是同一对象。
-
3.对于其他方式实现的单例模式,如果既想要做到可序列化,又想要反序列化为同一对象,则必须实现readResolve方法。
/**
* 枚举类下的单例模式
* @author Administrator
*/
public class MySingleton {
public enum MyEnumSingle {
INSTANCE;
private MySingleton singleOne;
private MyEnumSingle() {
System.out.println("初始化单例");
singleOne = new MySingleton();
}
public MySingleton getInstance() {
return singleOne;
}
}
private MySingleton() {
}
public static MySingleton getInstance() {
return MyEnumSingle.INSTANCE.getInstance();
}
}