volatile为什么可以保证内存可见性及防止指令重排序?

内存

共享主存和高速缓存(工作内存)。CPU高速缓存(L1,2)产生原因读写主存没有CPU执行指令快,他是某个CPU独有,只与该CPU运行的线程有关。

内存可见性

简单的说,CPU对数据的修改,对其他CPU立刻可见。下面我们详细地说。

  1. CPU修改数据,首先对工作内存修改,再同步主内存。单线程中,变量在工作内存的副本一直有效,CPU不用每次修改从主存读取变量,只是每次修改后同步主存。
  2. 对其他CPU立刻可见。当一个CPU修改变量,同步主存,如果其他CPU的工作内存也缓存这个变量,那么这个CPU的变量失效,当这个CPU想修改变量,必须从主存重新获取变量。

volatile保证内存可见性基于MESI协议实现的。MESI协议是缓存一致性协议一种。

题外话,如何保持CPU缓存一致性

第一种,操作系统在总线上发出lock信号,其他处理器既不能操作缓存了该共享变量内存地址的缓存,阻塞其他CPU,使该处理器可以独享此共享内存。总线锁定把CPU和内存的通信锁住,使得其他处理器在锁定期间不能操作其他内存地址的数据。

第二种,缓存一致性机制。当对某块CPU对缓存的数据操作后,通知其他CPU废弃存储在内部的缓存,或者从主存重新读取。

MESI协议

M:Modify,数据只在本CPU缓存,其他CPU没有,且数据修改没有更新到内存

E:Exclusive 独占。数据只在本CPU中缓存,且没有修改,即与内存中一致

S:Shared,数据在多个CPU都有缓存,且与内存一致

I:Invalid,本CPU中的这份缓存无效

该协议要求每个缓存行维护上面4个状态中的2个状态。CPU修改数据,这个CPU的数据状态更新M,其他CPU的数据状态更新I。

重排序

保证缓存一致性,所以在CPU的L1缓存设置store buffer\ load buffer。导致CPU执行顺序和程序不一致。

Memory Barrier

1.分割代码,阻止栅栏前后没有数据依赖性的代码进行指令重排序,保证程序一定程度有序

2.强制把store buffer/高速缓存的脏数据写回主存,缓存中相应数据失效,保证内存可见性

StoreLoad Barrier保证barrier前所有内存访问指令完成后,才执行barrier后内存访问指令

happens-before原则(重排序准则)

1. 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作;
2. 锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作;
3. volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作;
4. 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C;
5. 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作;
6. 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;
7. 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行; 对象终

volatile

采用memory barrier实现,保证可见性,禁止指令重排序,不保证原子操作。

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

可看,https://blog.csdn.net/ysq222/article/details/88771218

原文地址:https://www.cnblogs.com/ivy-xu/p/12587833.html

时间: 2024-08-03 21:50:47

volatile为什么可以保证内存可见性及防止指令重排序?的相关文章

关于volatile的可见性和禁止指令重排序的疑惑

在学习volatile语义的可见性和禁止指令重排序的相关测试中,发现并不能体现出禁止指令重排序的特性 实验代码如下 package com.aaron.beginner.multithread.volatiletest; import java.util.concurrent.CountDownLatch; /** * @author * @description 一句话描述该文件的用途 * @date 2017-03-01 */ public class VolatileAndNonVolat

Java的多线程机制系列:不得不提的volatile及指令重排序(happen-before)

一.不得不提的volatile volatile是个很老的关键字,几乎伴随着JDK的诞生而诞生,我们都知道这个关键字,但又不太清楚什么时候会使用它:我们在JDK及开源框架中随处可见这个关键字,但并发专家又往往建议我们远离它.比如Thread这个很基础的类,其中很重要的线程状态字段,就是用volatile来修饰,见代码 /* Java thread status for tools, * initialized to indicate thread 'not yet started' */   p

J.U.C JMM. pipeline.指令重排序,happen-before(续)

前面已经介绍硬件平台Cache Coherence问题和解决办法,下面来看看Java虚拟机平台的相关知识.硬件平台处理器,高速缓存,主存之间的交互关系如下: Java内存模型(JMM)        Java虚拟机规范中试图定义一种Java内存模型(Java Memory Model, JMM)来屏蔽掉底层各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果. 主内存和工作内存       Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟

JVM并发机制的探讨——内存模型、内存可见性和指令重排序

[转]http://my.oschina.net/chihz/blog/58035 文章写的非常好,为作者点赞. JAVA内存模型 对于我们平时开发的业务应用来说,内存应该是访问速度最快的存储设备,对于频繁访问的数据,我们总是习惯把它们放到内存缓存中,有句话不是说么,缓存就像是清凉油,哪里有问题就抹一抹.但是CPU的运算速度比起内存的访问速度还要快几个量级,为了平衡这个差距,于是就专门为CPU引入了高速缓存,频繁使用的数据放到高速缓存当中,CPU在使用这些数据进行运算的时候就不必再去访问内存.但

volatile关键字及内存可见性

先看一段代码: package com.java.juc; public class TestVolatile { public static void main(String[] args) { ThreadDemo td = new ThreadDemo(); new Thread(td).start(); while(true){ if(td.isFlag()){ System.out.println("----------------"); break; } } } } cla

volatile关键字与内存可见性

前言 首先,我们使用多线程的目的在于提高程序的效率,但是如果使用不当,不仅不能提高效率,反而会使程序的性能更低,因为多线程涉及到线程之间的调度.CPU上下文的切换以及包括线程的创建.销毁和同步等等,开销比单线程大,因此需谨慎使用多线程. 在jdk1.5以后,提供了一个强大的java.util.current包,这个包中提供了大量的应用于线程的工具类. 下面开始介绍volatile关键字和内存可见性,虽然volatile是在jdk1.5之前就有的,但还是想放在这里讲一下. 举例说明 首先,我们先看

轻量级的同步机制——volatile语义详解(可见性保证+禁止指令重排)

1.关于volatile volatile是java语言中的关键字,用来修饰会被多线程访问的共享变量,是JVM提供的轻量级的同步机制,相比同步代码块或者重入锁有更好的性能.它主要有两重语义,一是保证多个线程对共享变量访问的可见性,二防止指令重排序. 2.语义一:内存可见性 2.1 一个例子 public class TestVolatile { public static void main(String[] args) throws InterruptedException { ThreadD

volatile关键字与内存可见性&原子变量与CAS算法

1 .volatile 关键字:当多个线程进行操作共享数据时, 可以保证内存中的数据可见 2 .原子变量:jdk1.5后java.util.concurrent.atomic 包下提供常用的原子变量 3 .模拟CAS算法 TestVolatile package com.aff.juc; /* 1.volatile 关键字:当多个线程进行操作共享数据时, 可以保证内存中的数据可见 相较于synchronized是一种较为轻量级的同步策略 注意: volatile不具备"互斥性" 不能保

多线程之指令重排序

为什么会乱序 现在的CPU一般采用流水线来执行指令.一个指令的执行被分成:取指.译码.访存.执行.写回.等若干个阶段.然后,多条指令可以同时存在于流水线中,同时被执行. 指令流水线并不是串行的,并不会因为一个耗时很长的指令在"执行"阶段呆很长时间,而导致后续的指令都卡在"执行"之前的阶段上. 相反,流水线是并行的,多个指令可以同时处于同一个阶段,只要CPU内部相应的处理部件未被占满即可.比如说CPU有一个加法器和一个除法器,那么一条加法指令和一条除法指令就可能同时处