Java 实现单例模式有方法有双重检测锁,代码如下:
public class Singleton {
private static volatile Singleton singleton = null;
private Singleton(){}
public static Singleton getSingleton(){
if(singleton == null){
synchronized (Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
我理解的 synchronized 关键字实现了可见性、原子性和有序性,临界区中的代码可以重排序,但是不能重排序到临界区外面,synchronized 实现的可见性是临界区中代码执行结束之后,里面的共享变量会刷新到主内存中,那么如果 new Singleton() 方法被拆成了三个操作,并且经过重排序之后的顺序是这样的话:
- 分配内存
- 将实例引用赋值给 singleton 变量
- 实例初始化
不管这三个操作怎么重排序,另外一个线程看到的结果都是这三个操作执行完成后的结果(因为 synchronized 的原子性),那不就相当于另外一个线程访问到的 singleton 如果不为 null 的话就肯定实例化了吗?为什么还要多此一举加个 volatile 关键字禁止重排序呢?
我大概了解你的疑惑点,上面的评论其实已经可以解决你的疑惑了。
另外一个线程访问到的 singleton 如果不为 null 的话就肯定实例化了吗
问题的关键在于,Sychronized加锁的位置,它没有对getSingleton()整个方法解锁,而是判断singleton为null后才会去抢锁,所以多个线程可以同时进入getSingleton方法。
由于
synchronized
的有序性是持有相同锁的两个同步块只能串行的进入,即被加锁的内容要按照顺序被多个线程执行,但是其内部的同步代码还是会发生重排序,使块与块之间有序可见。那么如果不加上volatile防止指令的重排序,new Singleton() 方法被拆成了三个操作,并且经过重排序之后的顺序是这样的话:
其中线程A假设在sychronized块中将内存地址赋值给了对象,
其他线程此时调用getSingleton()
,发现singleton此时不为空了,那么直接返回singleton,但是此时singleton还未完成初始化
,那么问题就出现了。