DCL 单例模式
DCL 就是 Double Check Lock 的缩写,即双重检查的同步锁。代码如下,
public class Singleton {
// volatile防止指令重排
private static volatile Singleton singleton = null;
private Singleton(){
}
public static Singleton getInstance(){
//进入方法内,先判断实例是否为空,以确定是否需要进入同步代码块
if(singleton == null){
synchronized (Singleton.class){
//进入同步代码块时再次判断实例是否为空
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
DCL 单例线程安全问题
正常情况,可以保证调用 getInstance
方法两次,拿到的是同一个对象。但是,Java 中有个很强大的功能——反射通过反射,就可以破坏单例模式,从而调用它的构造函数,来创建不同的对象。
通过 反射 拿对象的hashcode
public static void main(String[] args) throws Exception {
Singleton singleton1 = Singleton.getInstance();
System.out.println(singleton1.hashCode());
Class<Singleton> clazz = Singleton.class;
Constructor<Singleton> ctr = clazz.getDeclaredConstructor();
ctr.setAccessible(true);
Singleton singleton2 = ctr.newInstance();
System.out.println(singleton2.hashCode());
}
打印结果如下:
singleton1: 458209687
singleton2: 233530418
通过反射就可以直接调用无参构造函数创建对象。打印出的 hashCode 不同,说明了这是两个不同的对象。即使构造函数private的
防止反射破坏单例
public class Singleton {
// volatile防止指令重排
private static volatile Singleton singleton = null;
private Singleton(){
if (singleton!=null){
System.out.println("************");
return;
}
}
public static Singleton getInstance(){
if(singleton == null){
synchronized (Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
singleton 不为空的时候直接return或者抛异常
序列化反序列化破坏
如下单列
public class Singleton {
private Singleton() {
}
public static Singleton getInstance() {
return SingleHolder.singleton;
}
private static class SingleHolder {
private static final Singleton singleton = new Singleton();
}
}
正常对象的获取,可以保持对象的单例存在
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1 == instance2); //输出true
序列化对单例的破坏
import java.io.*;
public class SingletonTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1 == instance2); //输出true
//将对象序列化到文件中
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton"));
oos.writeObject(instance1);
oos.close();
//将文件反序列化到对象
File file = new File("singleton");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
Singleton instance3 = (Singleton) ois.readObject();
System.out.println(instance1 == instance3);//输出false
}
}
debug,跟踪反序列化的执行过程,发现代码如下
OjbectInputStream -> readOrdinaryObject(boolean unshared)
如图,通过反射创建对象,而这也是原因所在,此处反序列化用的是该类的父类的无参构造函数来创建对象。
解决办法:添加下面代码
private Object readResolve() {
return getInstance();
}
反序列化的过程中,会检测该类是否包含readResolve方法,如果包含,就会通过反射调用该方法,而我们通过重写该方法,来保证单例不被反序列化破坏。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。