volatile关键字的作用及底层原理

volatile是什么?有什么作用?

volatile 是 java 虚拟机提供的轻量级同步机制,主要有两个特性:

  1. 保证可见性,也就是被 volatile 修饰的变量如果被一个线程修改,那么其他线程能够及时看到这个修改;
  2. 禁止指令重排。

volatile 是如何保证可见性的?

想要了解保证可见性的底层原理,首先我们需要对 CPU 读取内存的机制有所了解。
CPU 在处理数据时,线程首先把数据从内存读取到自己的缓存,再从缓存读到寄存器进行计算。如果我们的变量没有被 volatile 修饰,那么对变量的更改只会先被保存到缓存,再根据操作系统的策略定期推送回内存。这就导致有时候别的线程不能及时看到变量的修改,因为每个线程都有自己的缓存,不同缓存中同一个数据可能会不一致。
此时 volatile 来了,如果一个变量被volatile 修饰,那么在编译阶段,JVM 会在变量被修改的语句前面加一条 lock 语句,这个 lock 语句主要有两个作用:

  1. 把对该变量的更改推送到内存;
  2. 其他 CPU 可以嗅探到该指令,并把自己缓存中的该变量置为不可用,下次使用时需要从内存中读取。
    Easy,线程间的可见性解决。

volatile 是如何禁止指令重排的?

先简单概括一下:为了实现 volatile 的内存语义,编译器在生成字节码时,通过在指令序列中插入内存屏障来禁止特定类型的处理器重排序。
具体操作如下:

  • 在每个 volatile 写操作前面插入一个 StoreStore 屏障;
  • 在每个 volatile 写操作后面插入一个 StoreLoad 屏障;
  • 在每个 volatile 读操作后面插入一个 LoadLoad 屏障;
  • 在每个 volatile 读操作后面插入一个 LoadStore 屏障;

面试能答出这些感觉应该够了。

关于JMM的一些同步约定:

  1. 线程解锁前,必须把共享变量立刻刷回主存。
  2. 线程加锁前,必须读取主存中的最新值到工作内存(高速缓存)中。
  3. 加锁和解锁必须是同一把锁。
private static int num = 0;
public static void main(String[] args) {
    
    
    new Thread(()->{
    
    
        while (num == 0) {
    
    }
    }).start();
    try {
    
    
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
    
    
        e.printStackTrace();
    }
    num = 1;
    System.out.println(num);
}

在上面这个例子中,子线程在num等于0的条件下一直循环,即便主线程把num置为1,子线程中的num也不会改变,子线程还是在一直循环。

原文链接:java内存模型及volatile关键字解析

猜你喜欢

转载自blog.csdn.net/weixin_43390123/article/details/125079527