# volatile

它可以用来修饰成员变量和静态成员变量,他可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作volatile变量都是直接操作主存

volatile的底层实现原理是内存屏障

  • 对volatile变量的写指令后会加入写屏障
  • 对volatile变量的读指令前会加入读屏障

# 问题初形成

@Slf4j
public class Demo {
    static boolean run = true;
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            while(run){
                // ....
            }
        });
        t.start();
        TimeUnit.SECONDS.sleep(1);

        run = false; // 线程t不会如预想的停下来
    }

}

# 分析

  1. 初始状态, t 线程刚开始从主内存读取了 run 的值到工作内存 volatilewentichuxingsheng.png

  2. 因为 t 线程要频繁从主内存中读取 run 的值,JIT 编译器会将 run 的值缓存至自己工作内存中的高速缓存中,减少对主存中 run 的访问,提高效率 img.png

  3. 1 秒之后,main 线程修改了 run 的值,并同步至主存,而 t 是从自己工作内存中的高速缓存中读取这个变量的值,结果永远是旧值 img.png

# 解决方法

使用volatile

@Slf4j
public class Demo {
    static volatile boolean run = true;
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            while(run){
                // ....
            }
        });
        t.start();
        TimeUnit.SECONDS.sleep(1);

        run = false; // 线程t不会如预想的停下来
    }

}

注意

前面例子体现的实际就是可见性,它保证的是在多个线程之间,一个线程对volatile变量的修改对另一个线程可见,不能保证原子性,仅用在一个写线程,多个读线程的情况:

# 有序性

JVM会在不影响正确性的前提下,可以调整语句的执行顺序,思考下面一段代码

static int i;
static int j;
// 在某个线程内执行如下赋值操作
i = ...; 
j = ...;
  • 可以看到,至于是先执行 i 还是 先执行 j ,对最终的结果不会产生影响。所以,上面代码真正执行时,既可以是
i = ...; 
j = ...;
  • 也可以是
j = ...;
i = ...;

# 可见性

  • 写屏障(sfence)保证在该屏障之前的,对共享变量的改动,都同步到主存当中
  • 写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏
public void actor2(I_Result r) {
    num = 2;
    ready = true; // ready 是 volatile 赋值带写屏障
    // 写屏障
}
  • 读屏障(lfence)保证在该屏障之后,对共享变量的读取,加载的是主存中最新数据
  • 读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前
public void actor1(I_Result r) {
    // 读屏障
    // ready 是 volatile 读取值带读屏障
     if(ready) {
         r.r1 = num + num;
     } else {
         r.r1 = 1;
     }
}