实现单例模式的方法的8种之一:双重检查方式实现
public class SingleTonSample { private static SingleTonSample singleTonSample; private SingleTonSample(){} public static SingleTonSample getInstance(){ if (singleTonSample==null){ synchronized (SingleTonSample.class){ if (singleTonSample==null){ singleTonSample= new SingleTonSample(); } } } return singleTonSample; } }
乍一看这段代码是没有问题的,但是如果多线程操作的时候下面这段代码是会出现重排序问题的
singleTonSample= new SingleTonSample();
为什么呢?
创建一个对象其实是有三个步骤的
第一步:在内存中给singleTonSample分配内存空间
第二步:调用SingleTonSample的构造方法并执行构造方法中的代码
第三步: singleTonSample指向分配的内存空间
可见,我们创建一个对象并不是一个原子操作,所以如果在多线程来操作的时候出现重排序后果照样很严重
举个栗子:第一步如果Thread1进入这段代码
if (singleTonSample==null){ singleTonSample= new SingleTonSample(); }
创建对象这个过程中发生了重排序,导致第三步在第二步执行之前执行了
第一步:在内存中给singleTonSample分配内存空间
第三步: singleTonSample指向分配的内存空间
第二步:调用SingleTonSample的构造方法并执行构造方法中的代码
那么构造函数中的代码并未执行(在构造函数中可能有很多业务代码,比如连接数据库,初始化数据什么的...)就给singleTonSample进行了赋值。恰巧第二个线Thread2程抢到了锁 这时判断singleTonSample是不为空的,便把Thread1的执行的结果给拿到,那么这是有很大问题的,它那拿到的其实只是一个空壳子,同时Thread2使用构造方法里面的属性的时候便会报空指针错误 这是很严重的问题。
怎么解决呢?
其实很简单只需要加一个volatile关键字即可,因为volatile有禁止重排序的功能,正确方法如下
private volatile static SingleTonSample singleTonSample;
至此一个完美的双重检查方法实现的单例模式便写完:
public class SingleTonSample { private volatile static SingleTonSample singleTonSample; private SingleTonSample(){} public static SingleTonSample getInstance(){ if (singleTonSample==null){ synchronized (SingleTonSample.class){ if (singleTonSample==null){ singleTonSample= new SingleTonSample(); } } } return singleTonSample; } }