深入单例
破坏单例的两种方式
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也会破坏单列