写点什么

Volatile 原理六:图解指令重排

作者:悟空聊架构
  • 2021 年 12 月 14 日
  • 本文字数:973 字

    阅读完需:约 3 分钟

指令重排的例子

设想一下这种场景:定义了变量 num=0 和变量 flag=false,线程 1 调用初始化函数 init()执行后,线程调用 add()方法,当另外线程判断 flag=true 后,执行 num+100 操作,那么我们预期的结果是 num 会等于 101,但因为有指令重排的可能,num=1 和 flag=true 执行顺序可能会颠倒,以至于 num 可能等于 100


public class VolatileResort {    static int num = 0;    static boolean flag = false;    public static void init() {        num= 1;        flag = true;    }    public static void add() {        if (flag) {            num = num + 5;            System.out.println("num:" + num);        }    }    public static void main(String[] args) {        init();        new Thread(() -> {            add();        },"子线程").start();    }}
复制代码


先看线程 1 中指令重排:


num= 1;flag = true; 的执行顺序变为 flag=true;num = 1;,如下图所示的时序图



如果线程 2 num=num+5 在线程 1 设置 num=1 之前执行,那么线程 2 的 num 变量值为 5。如下图所示的时序图。


8.4 volatile 怎么实现禁止指令重排?

我们使用 volatile 定义 flag 变量:


static volatile boolean flag = false;
复制代码


如何实现禁止指令重排:


原理:在 volatile 生成的指令序列前后插入内存屏障(Memory Barries)来禁止处理器重排序。


有如下四种内存屏障:



volatile 写的场景如何插入内存屏障:


  • 在每个 volatile 写操作的前面插入一个 StoreStore 屏障(写-写 屏障)。

  • 在每个 volatile 写操作的后面插入一个 StoreLoad 屏障(写-读 屏障)。



StoreStore 屏障可以保证在 volatile 写(flag 赋值操作 flag=true)之前,其前面的所有普通写(num 的赋值操作 num=1) 操作已经对任意处理器可见了,保障所有普通写在 volatile 写之前刷新到主内存。


volatile 读场景如何插入内存屏障:


  • 在每个 volatile 读操作的后面插入一个 LoadLoad 屏障(读-读 屏障)。

  • 在每个 volatile 读操作的后面插入一个 LoadStore 屏障(读-写 屏障)。



LoadStore 屏障可以保证其后面的所有普通写(num 的赋值操作 num=num+5) 操作必须在 volatile 读(if(flag))之后执行。


作者简介:悟空,8 年一线互联网开发和架构经验,用故事讲解分布式、架构设计、Java 核心技术。《JVM 性能优化实战》专栏作者,开源了《Spring Cloud 实战 PassJava》项目,公众号:`悟空聊架构`。本文已收录至 www.passjava.cn

发布于: 3 小时前阅读数: 10
用户头像

用故事、大白话讲解Java、分布式、架构设计 2018.05.06 加入

公众号:「悟空聊架构」 【个人博客】www.passjava.cn 【开源项目】基于 SpringCloud 的一套面试刷题系统 【Github】https://github.com/Jackson0714/PassJava-Platform

评论

发布
暂无评论
Volatile 原理六:图解指令重排