Volatile原理与分析

161 阅读1分钟

Volatile原理与分析

我们知道并发编程的三大特性:

  • 原子性:保证指令不会受到线程上下文切换的影响
  • 有序性:保证指令不会受 cpu 指令并行优化的影响
  • 可见性:保证指令不会受 cpu 缓存的影响

volatile可以保证共享变量的有序性和可见性,那么今天来分析和介绍其底层原理。

底层原理

底层实现原理是内存屏障,Memory Barrier (Memory Fence)

  • 对volatile变量的写指令后会加入写屏障
  • 对volatile变量的读指令后会加入读屏障
public void actor1(){
    num = 2;
    raedy = true; // raedy是volatile赋值带写屏障
    // 写屏障
}
public void actor2(Result r){
    // 读屏障
    // ready是volatile读取值带读屏障
    if(ready){
        r = num + num;
    } else {
        r = 1;
    }
}
sequenceDiagram
participant t1 as t1 线程
participant num as num = 0
participant ready as volatile ready = false
participant t2 as t2 线程
t1 -->> t1: num = 2
t1 ->> ready: ready = true
Note over t1, ready: 写屏障
Note over num, t2: 读屏障
t2 ->> ready: 读取ready=true
t2 ->> num: 读取num=2

如何保证可见性

写屏障:保证在改屏障之前的,对共享变量的改动,都同步到主存中

读屏障:保证在改屏障之后,对共享变量的读取,加载的是主存中的最新数据

如何保证有序性

写屏障会确保指令重排序时,不会将写屏障之后的代码排在屏障之后

读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前

不足

无法解决指令交错的问题

  • 写屏障仅仅是保证之后的读能够读到新的结果,但不能保证读跑到它前面去
  • 有序性的保证也只是保证了本线程内相关代码不被重排序

以i++为例:

sequenceDiagram
participant t1 as t1 线程
participant i as volatile i = 0
participant t2 as t2 线程
t2 -->> i: 读取i= 0
t1 -->> i: 读取i=0
t1 -->> t1: i+1
t1 -->> i: 写入i=1
t2 -->> t2: i-1
t2 -->> i: 写入i=-1

参考链接

JUC并发编程详解