目录:
  1.重排序场景
  2.追根溯源
  3.缓存一致性协议
  4.重排序原因

一、重排序场景

class ResortDemo {
int a = 0;
boolean flag = false; public void writer() {
a = 1; //
flag = true; //
} Public void reader() {
if (flag) { //
int i = a * a; //
……
}
}
}

当两个线程 A 和 B,A 首先执行writer() 方法,随后 B 线程接着执行 reader() 方法。线程B在执行操作4时,能否看到线程 A 在操作1对共享变量 a 的写入?

答案是:不一定能看到。

由于操作1和操作2没有数据依赖关系,编译器和处理器可以对这两个操作重排序;同样,操作3和操作4没有数据依赖关系,编译器和处理器也可以对这两个操作重排序。

二、追根溯源

  为了提升计算性能,CPU 从单核升级到了多核甚至用到了超线程技术最大化提高 CPU 的处理性能。CPU增加了高速缓存,操作系统增加了进程、线程,通过CPU时间片的切换最大化的提升CPU的使用率。
 

  通过高速缓存的存储交互很好的解决了处理器与内存的速度矛盾,但是也为计算机系统带来了更高的复杂度,因为它引入了一个新的问题,缓存一致性。

三、缓存一致性协议

  同一份数据可能会被缓存到多个 CPU 中,如果在不同 CPU 中运行的不同线程看到同一份内存的缓存值不一样就会存在缓存不一致的问题。为了达到数据访问的一致,需要各个处理器在访问缓存时遵循一些协议,在读写时根据协议来操作,常见的协议有MSI,MESI,MOSI 等。最常见的就是 MESI 协议。

接下来
给大家简单讲解一下 MESI。
  MESI 表示缓存行的四种状态,分别是
  1. M(Modify) 表示共享数据只缓存在当前 CPU 缓存中,

并且是被修改状态,也就是缓存的数据和主内存中的数

据不一致

  2. E(Exclusive) 表示缓存的独占状态,数据只缓存在当前

CPU 缓存中,并且没有被修改
  3. S(Shared) 表示数据可能被多个 CPU 缓存,并且各个缓存中的数据和主内存数据一致
  4. I(Invalid) 表示缓存已经失效
 

                                             

  对于 MESI 协议,从 CPU 读写角度来说会遵循以下原则:
  CPU 读请求:缓存处于 M、E、S 状态都可以被读取,I 状态 CPU 只能从主存中读取数据。
  CPU 写请求:缓存处于 M、E 状态才可以被写。对于 S 状态的写,需要将其他 CPU 中缓存行置为无效才可写。

四、重排序原因

  MESI 协议虽然可以实现缓存的一致性,但是也会存在一些问题。
  

  基于上图中的原因,CPU又引入了storeBuffers的缓冲区。CPU0 只需要在写入共享数据时,直接把数据写入到 storebufferes 中,同时发送 invalidate 消息,然后继续去处理其

他指令。当收到其他所有 CPU 发送了 invalidate acknowledge 消息时,再将 store bufferes 中的数据数据存储至 cache line中。最后再从缓存行同步到主内存。        
                                                                  

    这个时候,我们再来看上述标题一中的重排序场景。

    class ResortDemo {
     int a = 0;
     boolean flag = false;      public void writer() {
     a = 1; //1
     flag = true; //2
     }      Public void reader() {
     if (flag) { //3
     int i = a * a; //4
……
     }
     }
    }
  当执行1操作时,a的状态从S->M,此时,线程A会先把变更写入到storebuffers,然后发送invalidate去异步通知其他CPU线程,紧接着就执行了下面的2操作。
此时,可能1的变更还在storebuffers中,并未提交到主内存。什么时候会提交到主内存,也不确定。
所以,线程B调用read方法可能会出现,看到了flag的变更,但是看不到a的变更,就出现了重排序的现象。

cpu指令重排序的原理的更多相关文章

  1. CPU指令重排序与MESI缓存一致性

    一.重排序场景 class ResortDemo { int a = 0; boolean flag = false; public void writer() { a = 1; //1 flag = ...

  2. java高并发核心要点|系列4|CPU内存指令重排序(Memory Reordering)

    今天,我们来学习另一个重要的概念. CPU内存指令重排序(Memory Reordering) 什么叫重排序? 重排序的背景 我们知道现代CPU的主频越来越高,与cache的交互次数也越来越多.当CP ...

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

    一.不得不提的volatile volatile是个很老的关键字,几乎伴随着JDK的诞生而诞生,我们都知道这个关键字,但又不太清楚什么时候会使用它:我们在JDK及开源框架中随处可见这个关键字,但并发专 ...

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

    一.不得不提的volatile volatile是个很老的关键字,几乎伴随着JDK的诞生而诞生,我们都知道这个关键字,但又不太清楚什么时候会使用它:我们在JDK及开源框架中随处可见这个关键字,但并发专 ...

  5. 深入浅出Java并发包—指令重排序

    前面大致提到了JDK中的一些个原子类,也提到原子类是并发的基础,更提到所谓的线程安全,其实这些类或者并发包中的这么一些类,都是为了保证系统在运行时是线程安全的,那到底怎么样才算是线程安全呢? Java ...

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

    并发本来就是个有意思的问题,尤其是现在又流行这么一句话:“高帅富加机器,穷矮搓搞优化”. 从这句话可以看到,无论是高帅富还是穷矮搓都需要深入理解并发编程,高帅富加多了机器,需要协调多台机器或者多个CP ...

  7. 深入浅出 Java Concurrency (4): 原子操作 part 3 指令重排序与happens-before法则

    转: http://www.blogjava.net/xylz/archive/2010/07/03/325168.html 在这个小结里面重点讨论原子操作的原理和设计思想. 由于在下一个章节中会谈到 ...

  8. 轻松学JVM(二)——内存模型、可见性、指令重排序

    上一篇我们介绍了JVM的基本运行流程以及内存结构,对JVM有了初步的认识,这篇文章我们将根据JVM的内存模型探索java当中变量的可见性以及不同的java指令在并发时可能发生的指令重排序的情况. 内存 ...

  9. 【java多线程系列】java内存模型与指令重排序

    在多线程编程中,需要处理两个最核心的问题,线程之间如何通信及线程之间如何同步,线程之间通信指的是线程之间通过何种机制交换信息,同步指的是如何控制不同线程之间操作发生的相对顺序.很多读者可能会说这还不简 ...

随机推荐

  1. Java知识回顾 (18)Java 8、9、11的新特性

    Java 8 Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本. Oracle 公司于 2014 年 3 月 18 日发布 Java 8 ,它支持函数式编程,新的 Jav ...

  2. 【开发工具】- 设置Sublime支持韩文展示

    菜单栏 --> 首选项 --> 设置 -->User 添加以下代码 { "font_size": 12, "show_encoding":tr ...

  3. 基于Text-CNN模型的中文文本分类实战 流川枫 发表于AI星球订阅

    Text-CNN 1.文本分类 转眼学生生涯就结束了,在家待就业期间正好有一段空闲期,可以对曾经感兴趣的一些知识点进行总结. 本文介绍NLP中文本分类任务中核心流程进行了系统的介绍,文末给出一个基于T ...

  4. 查找单链表中倒数第k个结点

    本文转自:程序员面试题6--查找链表中倒数第k个结点 题目:输入一个单向链表,输出该链表中倒数第k个结点.链表的倒数第0个结点为链表的尾指针.链表结点定义如下: struct ListNode { i ...

  5. SparkStreaming+kafka Receiver模式

    1.图解 2.过程 1.使用Kafka的High Level Consumer API 实现,消费者不能自己去维护消费者offset,而且kafka也不关心数据是否丢失. 2.当向zookeeper中 ...

  6. Java多线程 常见问题整理

    线程 什么是线程 线程是指程序在执行过程中,能够执行程序代码的一个执行单元. 线程和进程的区别 线程:一段程序执行过程中的一个执行单元,各个线程之间共享程序的内存空间以及一些进程级的资源,各线程拥有自 ...

  7. PHP java时间戳转php时间戳

    /** * java时间戳转php时间戳 * @param int $javaUt java的时间戳 * @return int * @Date 2019/8/26 */ public static ...

  8. MySQL DDL--gh-ost学习

    gh-ost工作原理 1.首先新建一张ghost表,结构与源表相同 2.使用alter命令修改ghost表 3.1.模拟从库命令获取主库上该表的binlog(基于全镜像的行模式的binlog包含更改前 ...

  9. golang错误处理

    1. 错误 错误用内建的error类型来表示. type error interface { Error() string } error 有了一个签名为 Error() string 的方法.所有实 ...

  10. django引用模板报错Template file 'index.html' not found

    产生报错的原因在于index.html未被引用到,处理办法是在setting.py中加入app的name. 我这里app的名字叫sign,所以加入sign就好了 再次访问就好啦