【Java并发编程】volatile(二):防止指令重排序 我会带着你远行 2022-11-19 04:20 320阅读 0赞 在[上一篇文章][Link 1]我们通过三个示例介绍了 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; } } [Link 1]: https://blog.csdn.net/weixin_43935927/article/details/108630814
还没有评论,来说两句吧...