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