单例模式-双检锁就稳了?
前言
前两天和一位朋友聊到单例模式的双检锁,相信了解过线程并发的朋友对双检锁不陌生,也可以看我之前分享的文章 。
这位朋友问我:双检锁能解决反射攻击和序列化问题吗?说实话,正好碰到我的知识盲区了,恰逢今天同事也提到单例,简单了解一下相关知识。
双检锁
我们先来回顾一下双检锁的经典写法。
public class Singleton {
private static volatile Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
反射攻击
大家知道,反射是可以访问类中私有域的成员,即使构造方法声明为私有。我们验证一下想法。
public static void main(String[] args) throws Exception{
Singleton s1 = Singleton.getInstance();
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton s2 = constructor.newInstance();
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
System.out.println(s1 == s2);
}
输出结果:
可以看到通过反射我们绕过了双检锁校验,直接创建了一个全新的对象。
解决方案其实很简单,既然反射最终也是需要通过构造方法创建对象,那么我们只需要在构造方法中加入相关校验即可。
public class Singleton {
private static Singleton instance = null;
private static int count = 0;
private Singleton() {
synchronized (Singleton.class) {
// 确保只创建一个对象实例
if(count > 0){
throw new RuntimeException("创建了两个实例");
}
count++;
}
}
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
序列化
上面的反射攻击比较好理解,那为什么序列化也能破坏单例呢?做一下实验。
class Singleton implements Serializable {
private static final long serialVersionUID = 1L;
String name;
private Singleton() {
}
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
public static void main(String[] args) throws Exception {
Singleton s1 = null;
Singleton s = Singleton.getInstance();
FileOutputStream fos = null;
ObjectOutputStream oos = null;
FileInputStream fis = null;
ObjectInputStream ois = null;
// 将对象序列化到文件中
try {
fos = new FileOutputStream("Singleton.obj");
oos = new ObjectOutputStream(fos);
oos.writeObject(s);
} finally {
oos.flush();
oos.close();
fos.close();
}
// 将对象从文件当中反序列化
try{
fis = new FileInputStream("Singleton.obj");
ois = new ObjectInputStream(fis);
s1 = (Singleton) ois.readObject();
}finally{
ois.close();
fis.close();
}
System.out.println(s.hashCode());
System.out.println(s1.hashCode());
System.out.println(s == s1);
}
}
输出结果:
可以看到序列化破坏了单例,反序列化的结果是另外一个对象。
总结
通过这篇文章,我们了解了:
反射攻击破坏单例模式,以及应对方法
序列化破坏单例
相信大家对序列化破坏单例还留有疑问,在下一篇文章中,我会提到相关原理以及应对方法。
如果觉得文章对你有帮助,欢迎留言点赞。
长按扫码可关注