在上一篇文章我们通过三个示例介绍了 volatile 能够保证多线程环境下的可见性,而 volatile关键字另一个作用就是禁止指令重排优化,从而避免多线程环境下程序出现乱序执行的现象。
下面看一个非常典型的禁止重排优化的例子DCL,如下:
public class Singleton {
private static Object lock = new Object();
private static Singleton instance = null;
private Singleton() {} // 私有构造器,防止被外部类实例化
public static Singleton getInstance() {
if(instance == null) { // 第一次检查
synchronized(lock) {
if(instance == null) { // 第二次检查(防止拿锁期间对象已被创建)
instance = new Singleton();
}
}
}
return instance;
}
}
上述代码一个经典的单例的双重检测的代码,这段代码在单线程环境下并没有什么问题, 但如果在多线程环境下就可以出现线程安全问题。原因在于某一个线程执行到第一次检测,读取到的instance不为null时,instance的引用对象可能没有完成初始化。
因为instance = new Singleton();可以分为以下3步完成(伪代码)
memory = allocate(); // 1.分配对象内存空间
instance(memory); // 2.初始化对象
instance = memory; // 3.设置instance指向刚分配的内存地址,此时instance!=null
由于步骤1和步骤2间可能会重排序,如下:
memory=allocate(); // 1.分配对象内存空间
instance=memory; // 3.设置instance指向刚分配的内存地址,此时instance!=null,但是对象还没有初始化完成!
instance(memory); // 2.初始化对象
由于步骤2和步骤3不存在数据依赖关系,而且无论重排前还是重排后程序的执行结果在单线程中并没有改变,因此这种重排优化是允许的。
但是指令重排只会保证串行语义的执行的一致性(单线程),但并不会关心多线程间的语义一致性。所以当一条线程访问instance不为null 时,由于instance实例未必已初始化完成(半初始化),也就造成了线程安全问题。
那么该如何解决呢?很简单,我们使用 volatile 禁止instance变量被执行指令重排优化即可
private volatile static Singleton instance = null;
完整代码如下:
public class Singleton {
private static Object lock = new Object();
private volatile static Singleton instance = null; // volatile(防止半初始化)
private Singleton() {} // 私有构造器,防止被外部类实例化
public static Singleton getInstance() {
if(instance == null) { // 第一次检查
synchronized(lock) {
if(instance == null) { // 第二次检查(防止拿锁期间对象已被创建)
instance = new Singleton();
}
}
}
return instance;
}
}
本文标题:【Java并发编程】volatile(二):防止指令重排序
本文链接:https://blog.quwenai.cn/post/10149.html
版权声明:本文不使用任何协议授权,您可以任何形式自由转载或使用。






还没有评论,来说两句吧...