五种单例模式的安全性问题

时间:2021-07-19 13:31:53

在之前两讲中,介绍了懒汉式、饿汉式双重锁、内部类、枚举5种单例模式,其实单例模式还有很多种设计,在此就不一一介绍了。

在这5种单例模式中,枚举最为特殊,由于是官方提供的一种模式,所以不能被破解,是十分安全的。

在剩余4种中,我们以懒汉式为例说说安全问题。

正常调用代码:

public class Client {
public static void main(String[] args) {
Husband01 s011 = Husband01.getWife();
Husband01 s012 = Husband01.getWife();

System.out.println(s011);
System.out.println(s012);
System.out.println(s011 == s012);
}
}

输出结果
Husband01@c3c749
Husband01@c3c749
true

接下来,用反射破解单例模式:

import java.lang.reflect.Constructor;
public class Client {
public static void main(String[] args) throws Exception {
Husband01 s011 = Husband01.getWife();
Husband01 s012 = Husband01.getWife();

//利用反射创建新对象
Class<Husband01> h1 = (Class<Husband01>) Class.forName("Husband01");//反射获取H1对象
Constructor c = h1.getDeclaredConstructor(null);//获取无参构造函数
c.setAccessible(true);//更改无参构造函数权限为公开
Husband01 s013 = (Husband01) c.newInstance(null);//创建新对象

System.out.println(s011);
System.out.println(s012);
System.out.println(s013);
System.out.println(s011 == s012);
System.out.println(s011 == s013);
}
}
输出结果

Husband01@1bc4459
Husband01@1bc4459
Husband01@12b6651
true
false
另一种,利 用反序列化破解(需实现Serializable)单例模式:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Client {
public static void main(String[] args) throws Exception {
Husband01 s011 = Husband01.getWife();
Husband01 s012 = Husband01.getWife();

//利用反序列化创建新对象
FileOutputStream fos = new FileOutputStream("d:/a.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s011);
fos.close();
oos.close();

FileInputStream fis = new FileInputStream("d:/a.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
Husband01 s013 = (Husband01) ois.readObject();
fis.close();
ois.close();

System.out.println(s011);
System.out.println(s012);
System.out.println(s013);
System.out.println(s011 == s012);
System.out.println(s011 == s013);
}
}
输出结果

Husband01@13bad12
Husband01@13bad12
Husband01@1a626f
true
false
我们现在知道了如果破解,那么如果避免呢?

代码应该这样写!

import java.io.ObjectStreamException;
import java.io.Serializable;

/**
* 懒汉式模式:在类加载时不初始化,当需要时再反馈(延迟加载)
*/
public class Husband01 implements Serializable{
//这句话看起来很奇怪,就理解为一开始每个男人都是没有老婆的
private static Husband01 wife = null;
//私有化老公,让小三无法拥有
private Husband01(){
if(wife != null){//防止反射破解
throw new RuntimeException();
}
}

//当家里人找你要老婆的时候
public static synchronized Husband01 getWife(){//添加synchronized防止小三插入问题
if(wife == null){
wife = new Husband01();//这句话理解成老公和老婆结婚啦!
}
return wife;
}

// 防止反序列化破解
private Object readResolve() throws ObjectStreamException {
return wife;
}
}
这样子,我们就能避免被破解啦?那么五种设计模式的效率如何呢?请看 5种单例模式的效率问题