ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

happens-before中 volatile 原则详解

2022-01-14 18:03:06  阅读:183  来源: 互联网

标签:happens ViewModelManager 屏障 线程 操作 volatile 排序 before


前言:本篇文章中主要讲解 happens-before 中关于 volatile 原则的理解。
volatile 变量规则:对一个volatile域的写,happens-before 于任意后续对这个 volatile 域的读。

一、volatile 关键字的作用:

  1. 可见性:一个线程对共享变量的修改,另一个线程获取到的值一定是修改后的。
    测试代码如下:
public class TestVolatile {
    static boolean stop = false;
    public static void main(String[] args) throws InterruptedException {
        
        new Thread("线程1") {
            @Override
            public void run() {
                while (!stop) {
                }
                System.out.println("线程停下来了");
            }
        }.start();
        TimeUnit.MILLISECONDS.sleep(200);
        stop = true;
        System.out.println("需要停下来 >>> " + stop);
    }
}

可以看到 主线程休眠200毫秒之后,设置 stop = ture,但是线程1根本没停下来,这就是可见性问题。
可以通过在 变量 stop 前面加上 volatile 关键字解决,大家可以自己验证。
在这里插入图片描述 2. 禁止指令重排序
经典的例子就是 DCL 单例:

class ViewModelManager {
    private ViewModelManager(){
    }
    private static volatile ViewModelManager mInstance;
    public static ViewModelManager getInstance(){
        if (mInstance == null){
            synchronized (ViewModelManager.class){
                if (mInstance == null)
                    mInstance = new ViewModelManager(); // 注释【1】
            }
        }
        return mInstance;
    }
 }

// 注释【1】可以分为3步:

  1. 为 ViewModelManager 分配内存空间
  2. 初始化 ViewModelManager
  3. 赋值给 mInstance

但是步骤2步骤3是可以交换顺序的,这就导致其他线程获取到的ViewModelManager未初始化,导致功能异常。

二、volatile 内存屏障

为了实现 volatile 的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。对于编译器来说,发现一个最优布置来最小化插入屏障的总数几乎不可能,为此,JMM 采取保守策略。下面是基于保守策略的 JMM 内存屏障插入策略:

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

下面是保守策略下,volatile 插入内存屏障后生成的指令序列示意图:
在这里插入图片描述
注意StoreStore 保证上面的普通写操作对共享变量的修改已刷回主存。

volatile 插入内存屏障后生成的指令序列示意图:
在这里插入图片描述
可以简单的归纳为如下的表格:
在这里插入图片描述
从上面的表格可以看出一下几点:
(1)当第二个操作是volatile写时,不管第一个操作是什么,都不能重排序。这个规则确保volatile写之前的操作不会被编译器重排序到volatile写之后。
(2)当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。这个规则确保volatile读之后的操作不会被编译器重排序到volatile读之前。
(3)当第一个操作是volatile写,第二个操作是volatile读时,不能重排序。

三、理解什么是 happens-before 原则中的 volatile 规则

volatile 规则 :对一个volatile变量的写,happens-before 于任意后续对这个 volatile 变量的读。

举个例子:


class TestVolatile {
   private int a = 0;
   private volatile boolean flag = false;

  // 线程1
   public void write() {
       a = 1;          // 注释【1】
       flag = true;    // 注释【2】
       // 根据volatile写的内存屏障:volatile 写之前的操作禁止重排序到 volatile 写之后,
       // 且 volatile 写之后会将变量 flag 刷回主存,即 a = 1的值也会被刷回主存
   }

  // 线程2
   public void read() {
       if (flag) {     // 注释【3】
           int i =  a; // 注释【4】
           // 根据volatile读的内存屏障:volatile 读之后的操作禁止重排序到volatile读之前。
           // 所以这里面就会禁止指令重排序,只有 flag = true的时候才会将a的值赋给i。
       }
   }
}

标签:happens,ViewModelManager,屏障,线程,操作,volatile,排序,before
来源: https://blog.csdn.net/zhujiangtaotaise/article/details/122493682

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有