如下代码所示:
public class doubleCheck{
private static Instance instance;
public static Instance getInstance(){
if(instance==null){ //1
synchronized (doubleCheck.class){
if(instance==null){
instance = new Instance(); //2
}
}
return instance;
}
}
似乎看起来降低了直接synchronized修饰类的开销,但是存在了多线程不安全。
首先,当线程A第一次执行到//2的时候,线程B同时也执行//1,这个时候线程A可能会得到instance!= null的情况,然后返回该实例,但返回的是一个空的实例,
是因为真正的情况线程A还没有初始化完毕instance实例。
那么为什么线程B为什么会得到instance不为空的实例(实则是为空的实例)呢?
我们抛析//2程序
Instance instance = new Instance();
这一行的java代码,隐藏了很多底层的实现过程
可以粗略的列出3个步骤
1.首先jvm要为这个对象实例分配内存空间
2.初始化这个对象
3.将instance指向内存地址(@疑点@)
由于在处理器阶段时候,会出现系统优化的重排序问题,但要符合一些规则才能重排需,首先是一些关键字不允许中排序等其他,其次要保证重排序后的结果要一致(单线程情况下)。
所以在上述步骤2,3会被重排序,那么就会出现如下新的排序:
1.首先jvm分配内存空间
2.instance指向内存的地址
//这个时候线程B进来了,要执行//1标记的程序,得到instance引用不为空,因为他已经指向了一个内存的地址了。
3.初始化instance对象
这样我们就明了为什么对线程情况下这种双重排序是不安全的。
那么同时有人就要问了,那如何让和这个doubleCheckt安全呢,
很简单,上述过程中也说了重排序的问题,只要禁止重排序即可,添加个关键字
在代码中定义变量的时候改为:
private volatile static Instance instance;