volatile 核心原理

目录

    • 保证可见性原理
    • 保证有序性核心原理
    • Volatile 的局限性

volatile 在内存语义上有两个作用,一个作用是保证被 volatile 修饰的共享变量对每个线程都是可见的,当一个线程修改了被 volatile 修饰的共享变量之后,另一个线程能够立刻看到修改后的数据。另一个作用是禁止指令重排。

保证可见性原理

volatile 能够保证共享变量的可见性。
如果一个共享变量使用 volatile 修饰,则该共享变量所在的缓存行会被会被要求进行缓存一致性校验。当一个线程修改了 volatile 修饰的共享变量之后,修改后的共享变量的值会立刻刷新到主内存,其他线程每次都从主内存中读取 volatile 修饰的共享变量,这就保证了使用 volatile 修饰的共享变量对线程的可见性。
例如:在程序中使用 volatile 修饰了一个共享变量 count,如下所示:

volatile long count = 0;

此时,线程对这个变量的读写都必须经过主内存。volatile 保证可见性的原理如图:
volatile 核心原理_第1张图片

保证有序性核心原理

volatile 能够禁止指令重排,从而能够避免在高并发的环境下多个线程之间出现乱序执行的情况。volatile 禁止指令重排是通过内存屏障实现的,内存屏障本质上就是一条 CPU 指令,这个 CPU 指令有两个作用,一个是保证共享变量的可见性,另一个是保证指令的执行顺序。volatile 禁止指令重排的规则如表:

是否可以重排序 第二个操作
第一个操作 普通读或写 volatile 读 volatile 写
普通写或读 可以重排序 可以重排序 不能重排序
volatile 读 不能重排序 不能重排序 不能重排序
volatile 写 可以重排序 不能重排序 不能重排序

为了实现上图的禁止指令重排的规则,JVM 编译器可以通过在程序编译生成的指令序列中插入「内存屏障」来禁止在内存屏障前后的指令发生重排。Java 内存模型建议 JVM 采用保守的策略严格禁止指令重排,volatile 读策略如图(1)所示:

  1. 在每个 volatile 读操作的后面都加入一个 LoadLoad 屏障,禁止后面的普通读与前面的 volatile 读发生指令重排。
  2. 在每个 volatile 读操作的后面都加入一个 LoadStore 屏障,禁止后面的普通写与前面的 volatile 读发生指令重排。

volatile 核心原理_第2张图片
volatile 写策略如图(2)所示:

  1. 在每个 volatile 写操作的前面都加入一个 StoreStore 屏障,禁止前面的普通写与后面的 volatile 写发生指令重排。
  2. 在每个 volatile 写操作的后面都加入一个 StoreLoad 屏障,禁止前面的 volatile 写与后面的 volatile 读发生指令重排。

volatile 核心原理_第3张图片
这种保守的内存屏障可以保证在任意 CPU 中都能够得到正确的执行结果。

Volatile 的局限性

volatile 虽然能够保证数据的可见性和有序性,但是无法保证数据的原子性。例如,下列代码中同时有两个线程对 volatile 修饰的 Long 类型的 count 值进行累加操作,count 的初始值为 0 ,每个线程都对 count 的值累加 1000 次,代码如下。

public class VolatileAtomicityTest {

    private volatile Long count = 0L;

    public void  incrementCount(){
        count++;
    }

    public Long execute() throws InterruptedException {
        Thread thread1 = new Thread(()->{
            IntStream.range(0, 1000).forEach((i) -> incrementCount());
        });

        Thread thread2 = new Thread(()->{
            IntStream.range(0, 1000).forEach((i) -> incrementCount());
        });

        //启动线程1和线程2
        thread1.start();
        thread2.start();

        //等待线程1和线程2执行完毕
        thread1.join();
        thread2.join();

        //返回count的值
        return count;
    }

    public static void main(String[] args) throws InterruptedException {
        VolatileAtomicityTest multiThreadAtomicity = new VolatileAtomicityTest();
        Long count = multiThreadAtomicity.execute();
        System.out.println(count);
    }
}

运行结果参考如下:
1448

你可能感兴趣的:(Java并发,java)