美文网首页
BiBi -2- 并发编程 - volatile

BiBi -2- 并发编程 - volatile

作者: 奋飞的蜗牛ing | 来源:发表于2018-12-23 14:20 被阅读12次

From:Java并发编程的艺术

2、volatile

  • volatile是轻量级的,保证“可见性”,体现高手的水平,因为:它不会引起线程上下文的切换和调度,比synchronized执行成本更低。

  • volatile可见性的本质实现:
    1)volatile修饰的变量会生成一个Lock前缀的指令,Lock前缀指令会引起处理器缓存会写到内存。
    2)一个处理器的缓存会写到内存会导致其他处理器的缓存无效。
    【采用缓存一致协议,每个处理器通过嗅探在总线程上传播数据来检查自己的缓存值是否过期。缓存一致性机制会阻止同时修改由两个以上处理器缓存的内存区域数据。】
    【用“缓存锁定”代替“总线锁定”,锁住总线导致其它CPU不能访问总线】
    嗅探技术:保证处理器内部缓存和系统内存的数据在总线上保持一致。如果嗅探到其它处理器在写内存地址,而这个地址处于共享状态,那么正在嗅探的处理器将使自己的缓存无效,下次访问相同地址时,强制执行缓存行填充,即从主内存中重新读取】

  • volatile优化的LinkedTransferQueue【通过追加字节的巧妙方法】
    背景:处理器的高速缓存行是64个字节宽,不支持部分填充缓存行。如果队列的头结点和尾结点都不足64字节,导致处理器将他们读到同一个高速缓存行中。
    缺陷:当一个处理器修改头结点时,由于锁定的缓存行中同时包含头结点和尾结点,在缓存一致性机制作用下,导致其它处理器不能访问自己高速缓存中的尾结点。
    改善:将共享变量【一个对象引用占4个字节】追加到64字节再填充到缓存行,避免头结点和尾结点在同一缓存行,使头结点和尾结点在修改时不会互相锁定,从而提高队列的入队和出队效率。

  • 特性
    任意单个volatile变量的读写具有原子性,如:a = 1; return b; c = 10L,其中对于64位的long/double的写也具有原子性。但对于多个volatile操作或volatile符合操作,如:i++,不具有原子性。

  • 可见性
    对一个volatile变量的读,总是能看到对这个volatile变量最后的写入。

  • 例子【体会volatile的使用场景】

class VolatileExample {
  int a = 0;
  volatile boolean flag = false;
  public void writer(){
    a = 1; // 1
    flag = true; // 2
  }
  public void reader(){
    if (flag){ // 3
      int i = a; // 4
    }
  }
}

线程A执行writer()方法之后,线程B执行reader()方法。根据happens-before规则,操作1 happens-before 操作4,因此可以正常运行。【volatile的写-读与锁的释放-获取具有相同的内存效果】【在线程B读一个volatile变量后,线程A在写这个volatile变量之前所有可见的共享变量的值都将立刻变得对线程B可见】【即操作2 执行完成后,会把共享变量 a 和 flag 刷新到主内存】

1)线程A写一个volatile变量,实质上是线程A向接下来将要读这个volatile变量的某个线程发出【对共享变量所做的修改的】消息。如:上述中的 a = 1。
2)线程B读一个volatile变量,实质上是线程B接收了之前某个线程发出的【在写这个volatile变量之对共享变量所做修改的】消息。如:上述中的 a = 1。

根据volatile重排序规则可知:操作 1 和操作 2 不会重排序;操作 3 和操作 4 不会重排序。

  • volatile重排序


    volatile重排序规则表

为了实现volatile内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。

  1. 在每个volatile写操作的面插入一个StoreStore屏障。
    StoreStore屏障将保障上面所有的普通写在volatile写之前刷新到主内存。
  2. 在每个volatile写操作的面插入一个StoreLoad屏障。
    StoreLoad避免volatile写与后面可能有的volatile读/写操作重排序。StoreLoad可以在每个volatile写后面,也可以在每个volatile读前面进行插入,但从整体效率角度考虑,常见的使用模式:一个线程写volatile变量,多个线程读volatile变量。所以JVM最终选择的是在每个volatile写后面插入StoreLoad屏障,
  3. 在每个volatile读操作的面插入一个LoadLoad屏障。
    LoadLoad禁止下面所有的普通读操作和上面的volatile读重排序。
  4. 在每个volatile读操作的面插入一个LoadStore屏障。
    LoadStore禁止下面所有的普通写操作和上面的volatile读重排序。

相关文章

网友评论

      本文标题:BiBi -2- 并发编程 - volatile

      本文链接:https://www.haomeiwen.com/subject/scwyhqtx.html