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