DCL 单例模式

321 阅读2分钟

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)

image-20210511185448044

如图,通过反射创建对象,而这也是原因所在,此处反序列化用的是该类的父类的无参构造函数来创建对象。

解决办法:添加下面代码

private Object readResolve() {
        return getInstance();
}

反序列化的过程中,会检测该类是否包含readResolve方法,如果包含,就会通过反射调用该方法,而我们通过重写该方法,来保证单例不被反序列化破坏。