DCL

头像
zensezz
    阅读 3 分钟

    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方法,如果包含,就会通过反射调用该方法,而我们通过重写该方法,来保证单例不被反序列化破坏。


    zensezz
    2 声望0 粉丝

    个人简介