Java之volatile如何保证可见性和指令重排序

1 我们先了解CPU缓存

CPU缓存为了解决CPU运算速度与内存读写速度不匹配的问题,因为CPU运算速度要比内存读写速度快得多

  • 一次主内存的访问通常在几十到几百个时钟周期
  • 一次L1高速缓存的读写只需要1~2个时钟周期
  • 一次L2高速缓存的读写也只需要数十个时钟周期

CPU大多数情况下读写都不会直接访问内存,取而代之的是CPU缓存,CPU缓存是位于CPU与内存之间的临时存储器(简单理解为寄存器),它容量比内存小得多但是交换速度却比内存快得多。而缓存中的数据是内存中的一小部分数据,但这一小部分是短时间内CPU即将访问的,当CPU调用大量数据时,就可先从缓存中读取,从而加快读取速度

CPU缓存可分为:一级缓存(是与CPU结合最为紧密的CPU缓存二级缓存三级缓存,每一级缓存中所存储的数据全部都是下一级缓存中的一部分

CPU要读取数据时,首先从一级缓存中查找,如果没有再从二级缓存中查找,如果还是没有再从三级缓存中或内存中查找。一般来说每级缓存的命中率大概都有80%左右,只剩下20%的总数据量才需要从二级缓存、三级缓存或内存中读取。

CPU执行计算的过程如下:

  • 程序以及数据被加载到主内存
  • 指令和数据被加载到CPU缓存
  • CPU执行指令,把结果写到高速缓存
  • 高速缓存中的数据写回主内存

2 总线锁

每个CPU都有一级缓存,但是,我们却无法保证每个CPU的一级缓存数据都是一样的,如何保证各个CPU缓存中的数据是一致的。就是CPU的缓存一致性问题

1)总线锁

一种处理一致性问题的办法是使用Bus Locking(总线锁)。当一个CPU对其缓存中的数据进行操作的时候,往总线中发送一个Lock信号。 这个时候,所有CPU收到这个信号之后就不操作自己缓存中的对应数据了,也就是把数据直接写入主内存,当操作结束,释放锁以后,所有的CPU就去内存中获取最新数据更新

3 volatile如何保证可见性

我们把有volatile修饰的变量编译成部分汇编,这里有个lock指令

0x01a3de24: lock addl $0X0,(%esp);

如果是写操作,cpu会发出一个lock指令,CUP会把数据直接写到到主内存

如果是读操作,cpu会发出一个unlock指令, 所有的CPU就去内存中获取最新数据更新

4 volatile如何保证指令重排序

现代的操作系统都是多处理器.而每一个处理器都有自己的缓存,并且这些缓存并不是实时都与内存发生信息交换.这样就可能出现一个cpu上的缓存数据与另一个cpu上的缓存数据不一致的问题.而这样在多线程开发中,就有可能导致出现一些异常行为.
而操作系统底层为了这些问题,提供了一些内存屏障用以解决这样的问题.目前有4种屏障.

  • LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
  • StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
  • LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
  • StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。

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

由于内存屏障的作用,避免了volatile变量和其它指令重排序 

参考链接:

https://crowhawk.github.io/2018/02/10/volatile/

https://www.jianshu.com/p/ef8de88b1343

https://my.oschina.net/LucasZhu/blog/1537330

(0)

相关推荐

  • 分布式并发编程,线程安全性,原理分析

    初步认识 Volatile 一段代码引发的思考 下面这段代码,演示了一个使用 volatile 以及没使用volatile这个关键字,对于变量更新的影响 public class VolatileDe ...

  • 【干货】连肝7个晚上,总结了关于Java基础的16个问题!

    说说进程和线程的区别? 进程是程序的一次执行,是系统进行资源分配和调度的独立单位,他的作用是是程序能够并发执行提高资源利用率和吞吐率. 由于进程是资源分配和调度的基本单位,因为进程的创建.销毁.切换产 ...

  • 到底什么是内存可见性?

    我们都知道,volatile保证了内存可见性和禁止指令重排,但是对于内存可见性这一条,我一直没有完全弄明白,今天咱们一起看一下,这个可见性,到底是如何可见,数据到底是如何可见的. 首先我们要达成一个共 ...

  • 看懂这篇,才能说了解并发底层技术

    零.开局 前两天我搞了两个每日一个知识点,对多线程并发的部分知识做了下概括性的总结.但通过小伙伴的反馈是,那玩意写的比较抽象,看的云里雾里晕晕乎乎的. 所以又针对多线程底层这一块再重新做下系统性的讲解 ...

  • volatile关键字的作用

    volatile关键字的作用 1.java内存模型. 如上图所示,所有线程的共享变量都存储在主内存中,每个线程都有一个独立的工作内存,每个线程不直接操作在主内存中的变量,而是将主内存上变量的副本放进自 ...

  • volatile关键字详解

    volatile的三个特点 保证线程之间的可见性 禁止指令重排 不保证原子性 可见性 概念 可见性是多线程场景中才讨论的,它表示多线程环境中,当一个线程修改了共享变量的值,其他线程能够知道这个修改. ...

  • 指令重排 内存屏障

    instance = new Single()这句,这并非是一个原子操作,在 JVM 中做了下面 3 件事情. 1. 给 instance 分配堆内存(Single 对象) 2. 调用 Single的 ...

  • 指令重排序与内存屏障

    从老版glibc的一个bug说开来 之前我在公众号中,发表过这么一篇文章: 这篇文章里面提到了老版本的glibc(2.13以前)中的排序函数qsort()有一个在并发时会出现core dump的bug ...

  • 补钙是保证河蟹后期增重的关键

    "钙"对于甲壳动物来说是必需的营养元素,尤其在脱壳的时候,除了提高体质.加速硬壳外,对于增加河蟹的重量起到了重要作用.因此在最后一次脱壳前后补充"钙"制剂意义重 ...

  • Java并发多线程编程——Volatile原理与使用

    优质文章,第一时间送达 76套java从入门到精通实战课程分享 一.volitile的理解 Volatile称之为轻量级锁,被volatile修饰的变量,在线程之间是可见的. 可见即一个线程修改了这个 ...

  • 大数据成神之路-Java高级特性增强(Volatile)

    语 本部分网络上有大量的资源可以参考,在这里做了部分整理,感谢前辈的付出,每节文章末尾有引用列表,源码推荐看JDK1.8以后的版本,注意甄别~ 多线程 集合框架 NIO Java并发容器 1volat ...

  • AMD提交GPU显存保护相关专利:保证指令正确运行、免受故障影响

    当前针对计算机硬件的攻击越来越多,而且这一方向也是当前信息安全的重要领域之一,除了信息安全研究人员外,硬件制造商也需要寻找方法保证其推出产品免受漏洞影响.虽然目前发现的很多针对硬件的攻击依旧是软件层面 ...

  • 超e保2021,虽不保证续保,但依然值得选!

    作为太平人寿旗下的王牌百万医疗险,超e保系列素来大卖. 近期新出的超e保2021也是大公司百万医疗险中非常不错的一位新秀. 太平超e保2021 首先来看保障责任. 该有的都有,保障责任齐全! 特别是重 ...

  • 做包子馒头如何快速发酵?我来教你技巧, 保证面团快速发起来,蒸的包子馒头个个蓬松萱软不塌陷, 美味可...

    最近在家没事可做,许多人开启了自己的厨艺生涯.不过面对不少的面食都需要进过发面来制作,所以不少的朋友开始困惑,这个面要怎么发?要发多久的时间才能发好?怎么样才算是面发好了?还有的朋友在说,自己家的面怎 ...

  • JAVA多线程学习笔记整理

    多线程: 三种创建方法 继承Thread类,以线程运行内容重写run方法,创建Thread对象并用start方法启动该线程. (匿名内部类) (Lambda表达式) 实现Runable接口,以线程运行 ...

  • 为什么要选择学习Java?适合零基础的初学者的文章

    我经常收到这样的问题:"要学习的第一门编程语言是什么?" Java是一门好的编程语言吗?"和" Java是适合初学者的好的第一门编程语言,还是我应该从Java或 ...