zoukankan      html  css  js  c++  java
  • 从底层原理深度剖析volatile关键字

    本篇文章从底层原理层面深度剖析volatile关键字是如何实现内存可见性的,同时引入了Java内存模型、指令重排序以及内存屏障等知识点作为原理分析的知识支撑。

    阅读本文之前,推荐大家先阅读作者之前的一篇关于happens-before的文章,这样更有助于大家对volatile关键字底层原理的理解。

    面试官为什么总是问happens-before规则,看完这篇文章你就懂了

    简述Java内存模型

    Java内存模型分为主内存和线程工作内存两大类。

    主内存:多个线程共享的内存。如下图所示,方法区和堆属于主内存区域。

    线程工作内存:每个线程独享的内存。如下图所示,虚拟机栈、本地方法栈、程序计数器属于线程独享的工作内存。

    Java内存模型规定:所有变量都需要存储在主内存中,线程工作内存保存了变量在主内存中的副本,线程对变量的所有操作都在工作内存中进行,执行结束后在同步到主内存中去。这里必然会存在时间差,在这个时间差内,该线程对副本的操作,对于其他线程是不见的,从而造成了可见性问题。

    指令重排序

    JVM对代码进行编译优化,导致代码可能并不是按照代码编写顺序执行,而是按照JVM进行编译优化后的顺序执行。指令重排序对并发编程安全性有很大影响,所以提供了一些happens-before规则定义一些禁止编译优化的场景。

    volatile的作用

    保证共享变量的可见性:使用volatile修饰的变量,任何线程对其进行操作都是在主内存中进行的,不会产生副本,从而保证共享变量的可见性。

    防止局部指令重排序:happens-before规则中的volatile变量规则规定了一个线程先去写一个volatile变量,然后一个线程去读这个变量,那么这个写操作的结果一定对读的这个线程可见。

    volatile如何防止指令重排序

    volatile是通过内存屏障来防止指令重排序的。

    硬件层面的内存屏障分为Load Barrier 和 Store Barrier即读屏障和写屏障。

    对于Load Barrier来说,在指令前插入Load Barrier,可以让高速缓存中的数据失效,强制从新从主内存加载数据。对于Store Barrier来说,在指令后插入Store Barrier,能让写入缓存中的最新数据更新写入主内存,让其他线程可见。Java内存屏障类型把上述两种内存屏障两两组合,如下图所示:

    volatile防止指令重排序具体步骤:

    1.在每个volatile写操作的前面插入一个StoreStore屏障。

    2.在每个volatile写操作的后面插入一个StoreLoad屏障。

    3.在每个volatile读操作的后面插入一个LoadLoad屏障。

    4.在每个volatile读操作的后面插入一个LoadStore屏障。

    volatile写内存屏障示意图 

     volatile读内存屏障示意图

    volatile总结

    volatile解决的是多线程共享变量可见性问题,但是被volatile修饰的变量操作并非具有原子性。如下面代码所示:

    上述代码两个线程同时执行count++操作1000次,多次执行结果均不为2000,可见被volatile修饰的变量操作不具有原子性。注:可以通过对count++加锁的方式或使用AtomicLong和LongAdder(JDK8推荐使用)类来实现count++的原子性。

    zz:https://baijiahao.baidu.com/s?id=1655055831382625926&wfr=spider&for=pc

  • 相关阅读:
    Java实现 计蒜客 拯救行动
    Java实现 计蒜客 拯救行动
    Java实现 LeetCode 174 地下城游戏
    Java实现 LeetCode 174 地下城游戏
    Java实现 LeetCode 174 地下城游戏
    Java实现 LeetCode 173 二叉搜索树迭代器
    Java实现 LeetCode 173 二叉搜索树迭代器
    Visual Studio的SDK配置
    怎样使用CMenu类
    mfc menu用法一
  • 原文地址:https://www.cnblogs.com/erichi101/p/13159194.html
Copyright © 2011-2022 走看看