zoukankan      html  css  js  c++  java
  • 正确理解volatile与happens-before

    1. 双重校验锁实现单例的问题

    在延迟实现单例时,一般代码形式如下:

     1 public class Foo {
     2     private static volatile Foo instance;
     3 
     4     public static Foo getInstance() {
     5         //第一次检查,不锁定
     6         if (null == instance) {
     7             //一旦初始化,第一次检查将无法通过,不会有锁定开销
     8             synchronized (Foo.class) {
     9                 //第二次检查,锁定
    10                 if (null == instance) {
    11                     instance = new Foo();
    12                 }
    13             }
    14         }
    15         return instance;
    16     }
    17 }

    看起来很简单,但这里有个容易忽略的点,就是instance变量,需要用volatile修饰。

    为什么?如果不加的话会有什么问题呢?

    让我们把目光聚焦到第11行,初始化instance变量。这一行代码可以分解为如下3行伪代码。

    memory = allocate(); //1. 分配对象的内存空间
    
    ctorInstance(memory);//2. 初始化对象
    
    instance = memory; //3.设置instance指向刚分配的内存地址

    上面伪代码中的2和3,是可能会被重排序的,重排序后将变成如下时序:

    memory = allocate(); //1. 分配对象的内存空间
    
    instance = memory; //3.设置instance指向刚分配的内存地址,注意,此时对象未初始化。 
    
    ctorInstance(memory);//2. 初始化对象

    那么在多线程并发的场景下,假设有两条线程AB同时访问这个方法,可能发生以下的访问顺序:

    时间                            线程A                                                     线程B

    1                          分配内存空间(对应1)                       

    2               设置instance指向内存空间(对应3)

    3                                                                                    判断instance是否为null

    4                                                                                 由于instance不为null, 不再等待进行临界区,直接访问instance引用的对象

    5                           初始化对象(对应2)

    这样一来,B在访问instance变量时,可能由于instance未初始化而导致出现一些异常。

    那么,为什么加上volatile修饰就可以避免这种情况呢?这就涉及到happens-before和volatile的语义了。

    2.happens-before与volatile语义

    从JDK5开始,Java使用新的JSR-133内存模型。JSR-133使用happens-before的概念来阐述操作之间的内存可见性。在JMM中,如果一个操作执行的结果需要对另一个操作可见,以这两个操作之间必须要有happens-before关系。这里提到的两个操作,既可以是在一个线程之内,也可以是在不同的线程之间。

    与程序员密切相关的happens-before规则如下。

    1)程序顺序规则:一个线程中的每个操作,happens-before于该线程的任意后续操作。

    2)监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。

    3)volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。

    4)传递性:如果A happens-before B,B happens-before C,那么A happens-before C.

    理解volatile特性的一个好方法是把对volatile变量的单个读/写,看成是使用同一个锁对这些单个读/写操作做了同步。也就是说,对任意单个volatile变化的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性。

    那么上面第三条规则是怎么实现的呢?请参考:深入理解Java内存模型(四)——volatile

    简单来说,就是在volatile写之后加入了一个StoreLoad屏障,防止后面的读与前面的写重排序了。这样后面的线程读到的,就是一个完整的对象。

    参考:《Java并发编程的艺术》

  • 相关阅读:
    海量的数据的处理
    leetcode(135)分发糖果
    leetcode(130)被围绕的区域
    排序算法
    leetcode(956)最高的广告牌
    kafka的配置
    kafka命令行
    leetcode(76)最小覆盖子串
    Vue开发全解
    idea eclipse 设置代理
  • 原文地址:https://www.cnblogs.com/kingsleylam/p/8698681.html
Copyright © 2011-2022 走看看