单例模式中使用volatile的目的:禁止指令重排序
DCL(Double Check Lock)双端检锁机制不一定线程安全,因为:在某个线程执行到第一次检测时,读取到的instance不为null时,instance对象可能没有完成初始化。
解释:
instance = new SingletonPattern();可以分为以下3步完成(伪代码)
1.分配内存对象空间:memory = allocate();
2.初始化对象:instance = (memory);
3.设置instance指向刚分配的内存地址,此时instance != null :instance = memory;
由于步骤2和步骤3不存在数据依赖关系,而且无论重排前还是重排后程序的执行结果在单线程中并没有改变,因此这种重排优化是允许的。
发生指令重排的情况:
1.分配内存对象空间:memory = allocate();
3.设置instance指向刚分配的内存地址,此时instance != null,但是对象初始化没有完成,就相当于这个地址空间内没有实际的值:instance = memory;
2.初始化对象:instance = (memory);
/**
* 单例模式
*/
public class SingletonPattern {
/**
* 定义一个私有的静态singleton变量
* volatile: 防止指令重排序
*/
private static volatile SingletonPattern singleton = null;
//私有的构造方法,限制其它类直接new一个实例对象,要获取对象,必须使用下面的getInstance()方法
private SingletonPattern() {
System.out.println("我是无参数的构造方法!!!");
}
/**
* 静态的方法,可以使用类名调用,同时也可以直接使用静态变量singleton
* 必须使用双重锁:当a,b两个线程都执行到同步代码块这里时,a线程进入同步代码块,实例化一个Singleton(),结束代码块的执行;
* 这时b线程进来,如果不进行判断,又实例化一个Singleton(),singleton引用指向它; 之前的对象变为垃圾,待回收,没必要
* @return
*/
public static SingletonPattern getInstance() {
//第一个if是避免同步阻塞等待
if (singleton == null) {
synchronized (SingletonPattern.class) {
//第二个if是为了避免产生多个对象
if (singleton == null) {
singleton = new SingletonPattern();
}
}
}
return singleton;
}
参考链接:单例模式volatile分析