关于单例模式:DCL

9次阅读

共计 2483 个字符,预计需要花费 7 分钟才能阅读完成。

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 办法,如果蕴含,就会通过反射调用该办法,而咱们通过重写该办法,来保障单例不被反序列化毁坏。

正文完
 0