对不清楚单例模式的小伙伴可以参考我的另一篇文章:
在Java中单例模式存在这么一种情况如下:
/**
* DCL双锁校验
* 线程安全
* volatile关键字用于防止指令重排序
*/
class Singleton {
private static volatile Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
这种单例模式被称为DCL双重检查锁.
他是通过首先的第一次检查单例是否被初始化,然后进入锁的代码块,后再检查一次是否已经初始化单例,这样可以有效的避免在进过第一次的检查时,检查结果为true,但是实际上另一个线程已经准备初始化该单例了.然后导致初始化多个单例.
但是为什么需要volatile关键字呢?
首先我们要了解对象的创建过程(new关键字),它简单的分为三个阶段:
1.分配对象内存空间.
2.初始化对象.
3.设置对象指向内存空间.
那么实际上第三步和第二部的关系是可以进行互换的,在JVM的优化中存在一种指令重排序的现象,为了加快JVM的运行速度,
指令重排序会在不影响结果的情况下,对JVM的指令进行重新排序.
那么当出现指令重排序时,原本1,2,3的顺序则可能变为1,3,2.此时当代码运行到3时,另一个线程恰好在获取该单例,那么此时代码就会返回一个没有初始化完成的单例对象,这是非常危险的. (如下图所示)
现在说会volatile关键,这个关键字有两个作用
1.保证对象的可见性.
2.防止指令重排序.
今天来说的是它的第二个作用,当使用了volatile修饰这个单例时,JVM就能确保new一个对象的过程是1,2,3的顺序,自然就不会发布一个没有被初始化完成的对象了.