破坏单例

继上篇懒汉式单例模式,线程安全与性能都有所保障了,但是还会有其它方式破坏单例场景吗?答案是肯定的,这里我们介绍一下,反射与序列化两种破坏机制。

 反射破坏单例

按静态内部类单例为例,虽然单独的构造方法用了private,但是我们可以通过反馈强制来访问,然后再调用newInstance()方法,创建出两个不能的实例,请看测试类代码。

public class InnerClassTest {
	public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException,
			IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		// 反射破坏单例
		Class<?> clazz = LazyInnerClassSingleton.class;
		// 通过反射获取私有构造方法
		Constructor con = clazz.getDeclaredConstructor(null);
		// 强制访问
		con.setAccessible(true);

		// 调用两次,相当于创建了两个实例,破坏了单例
		Object obj1 = con.newInstance();
		Object obj2 = con.newInstance();
		// 判断
		System.out.println("obj1:" + obj1);
		System.out.println("obj2:" + obj2);
		System.out.println(obj1 == obj2);

	}
}

 核对运行结果,你会发现,反射破坏了单例,并且创建出了两个不同的实例

那么如何去避免这种现象的发生呢,我们只需要在原来的LazyInnerClassSingleton类中,构造方法里作个判断就可以,至此最强的单例模式实现了。

public class LazyInnerClassSingleton {
	private LazyInnerClassSingleton() {
		//第一次调用,会加载内部类完成实例
		//第二次调用,实例已经存在,则抛出异常
		if(MyInnerClass.sing != null) {
			throw new RuntimeException ("不允许创建多个实例");
		}
	};

	// 懒汉式单例,静态内部类
	// 如果方法没有使用,内部类不会加载
	public static final LazyInnerClassSingleton getInstance() {
		// 返回结果时,先加载内部类
		return MyInnerClass.sing;
	}

	// 默认不加载 ,只创建一次
	private static class MyInnerClass {
		private static final LazyInnerClassSingleton sing = new LazyInnerClassSingleton();
	}
}

调整后运行结果

序列化破坏单例

一个单例对象创建好后,有时候需要将对象序列化然后写入磁盘,下次使用时再从磁盘中读取对象并进行反序列化,将其转化为内存对象。反序列化后的对象会重新分配内存,即重新创建。如果序列化的目标对象为单例对象,就违背了单例模式的初衷,相当于破坏了单例,来看一段代码︰

public class SeriableSingleton implements Serializable {
	//序列化就是把内存中的状态通过转换成字节码的形式
	//从而转换一个I/O流,写入其他地方(可以是磁盘、网络I/0)
	//内存中的状态会永久保存下来
	
	//反序列化就是将己经持久化的字节码内容转换为I/0流
	//通过I/0流的读取,进而将读取的内容转换为Java对象
	//在转换过程中会苴新创注对象new
	private SeriableSingleton() {
	}

	private static final SeriableSingleton ser = new SeriableSingleton();

	public static SeriableSingleton getInstance() {
		return ser;
	}
}

测试类:

public class SerialbleTest {
	public static void main(String[] args) {
		SeriableSingleton s1 = null;
		SeriableSingleton s2 = SeriableSingleton.getInstance();

		try {// 序列化
			FileOutputStream fos = new FileOutputStream("SeriableSingleton.obj");
			ObjectOutputStream oos = new ObjectOutputStream(fos);
			oos.writeObject(s2);
			oos.flush();
			oos.close();
			// 反序列化
			FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
			ObjectInputStream ois = new ObjectInputStream(fis);
			s1 = (SeriableSingleton)ois.readObject();
			ois.close();
			// 得到两个对象不一致
			System.out.println("s1:" +  s1);
			System.out.println("s2:" +  s2);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

运行结果如下:

s1:dysf.singleton.seriable.SeriableSingleton@41629346
s2:dysf.singleton.seriable.SeriableSingleton@3d4eac69

那么,我们如何保证在序列化的情况下也能够实现单例模式呢?其实很简单,只需要增加readResolve()方法即可。来看优化后的代码∶SeriableSingleton 这个类中加入方法,具体如何实现的,请查看JDK源码进行分析。

//保障单例不被反序列化破坏
	private Object readResolve() {
		return ser;
	}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值