多线程并发-java内存模型和计算机基础

CPU缓存一致性协议MESI

CPU在摩尔定律的指导下以每18个月翻一番的速度在发展,然而内存和硬盘的发展速度远远不及CPU。这就形成了高性能能的内存和硬盘价格及其昂贵。然而CPU的高度运算须要高速的数据。为了解决这个问题,CPU厂商在CPU中内置了少许的高速缓存以解决I\O速度和CPU运算速度之间的不匹配问题。 在CPU访问存储设备时,不管是存取数据抑或存取指令,都趋于汇集在一片连续的区域中,这就被称为局部性原理。java

  1. 时间局部性(Temporal Locality):若是一个信息项正在被访问,那么在近期它极可能还会被再次访问。 好比循环、递归、方法的反复调用等。
  2. 空间局部性(Spatial Locality):若是一个存储器的位置被引用,那么未来他附近的位置也会被引用。 好比顺序执行的代码、连续建立的两个对象、数组等
带有高速缓存的CPU执行计算的流程
  1. 程序以及数据被加载到主内存
  2. 指令和数据被加载到CPU的高速缓存
  3. CPU执行指令,把结果写到高速缓存
  4. 高速缓存中的数据写回主内存
多核CPU多级缓存一致性协议MESI

MESI协议缓存状态数组

缓存行(Cache line):缓存存储数据的单元。缓存

状态 描述 监放任务
M 修改 (Modified) 该Cache line有效,数据被修改了,和内存中的数据不一致,数据只存在于本Cache中。 缓存行必须时刻监听全部试图读该缓存行相对就主存的操做,这种操做必须在缓存将该缓存行写回主存并将状态变成S(共享)状态以前被延迟执行。
E 独享、互斥 (Exclusive) 该Cache line有效,数据和内存中的数据一致,数据只存在于本Cache中。 缓存行也必须监听其它缓存读主存中该缓存行的操做,一旦有这种操做,该缓存行须要变成S(共享)状态。
S 共享 (Shared) 该Cache line有效,数据和内存中的数据一致,数据存在于不少Cache中。 缓存行也必须监听其它缓存使该缓存行无效或者独享该缓存行的请求,并将该缓存行变成无效(Invalid)。
I 无效 (Invalid) 该Cache line无效。

注意: 对于M和E状态而言老是精确的,他们在和该缓存行的真正状态是一致的,而S状态多是非一致的。若是一个缓存将处于S状态的缓存行做废了,而另外一个缓存实际上可能已经独享了该缓存行,可是该缓存却不会将该缓存行升迁为E状态,这是由于其它缓存不会广播他们做废掉该缓存行的通知,一样因为缓存并无保存该缓存行的copy的数量,所以(即便有这种通知)也没有办法肯定本身是否已经独享了该缓存行。多线程

从上面的意义看来E状态是一种投机性的优化:若是一个CPU想修改一个处于S状态的缓存行,总线事务须要将全部该缓存行的copy变成invalid状态,而修改E状态的缓存不须要使用总线事务。并发

cpu多级缓存 - 乱序执行优化

处理器为提升运算速度儿作出违背代码原有顺序的优化。性能

在单核处理器时代处理器的乱序执行优化不会影响执行结果。在多核处理中,某个核心执行写入操做时,将某个标志当作写入完成,进行重排优化,可能会先执行标志指令致使其余核心觉得改核心已经执行完成写入操做。从而拿到错误的值。优化

java内存模型(java memory model, JMM)

  1. 堆heap 堆是运行时肯定的内存,由java GC来维护大小,优势是能够动态的肯定大小,缺点是运行时动态肯定内存因此速度相对栈小一点。对象存放在堆上。静态变量跟随类一块儿存放在堆上。
  2. 栈stack 栈内存的速度相对堆内存更快,仅次于寄存器,缺点是大小必须是编译期肯定的。缺少必定的灵活性,存放一些基本的数据变量(int double。。。)java内存要求本地变量(Local Variable),调用栈必须存放在线程栈(Thead Stack)中。

本地变量可能存放的是对象的引用。当两个线程同时引用一个对象时,那么这两个线程的本地引用存放的是这个对象的私有拷贝。线程

硬件内存模型如图 3d

硬件内存模型和java内存模型的对应模型如图:

java内存抽象模型结构

看图,本地内存:本地内存是java抽象的概念,涵盖了缓存,写缓存区,寄存器,其余硬件和编译器优化。本地内存储存了共享变量的副本,从硬件的角度上讲主内存就是硬件内存,可是为了获取更好的速度,java可能会将数据存储在寄存器或者高速缓存区。若是线程要通讯必需要通过主内存,流程是先在主内存中获取共享变量,存储在本地内存中经由进程计算,而后刷新至主内存,再经由其余线程访问。

java内存模型- 同步操做与规则

  1. lock和Unlack:做用在主内存上只有在Unlock的状况下内存才能够被其余线程锁定。
  2. Read:做用在主内存上,把主内存中的变量输送在工做内存中。
  3. Load:做用工做内存中,把主内存中的值放入到工做内存副本中。
  4. use:做用于工做内存,把数据给执行引擎。每当执行器须要使用到变量时或者执行字节码指令时会执行这个操做。
  5. assign:赋值,在执行赋值操做时执行,将执行引擎中的值赋值给工做内存。
  6. store:存储,把工做内存中的值传递到主内存中。
  7. write:写入,将工做内存中的值写入到主内存中。

下面介绍一下规则,规则是用来限制每一步是如何操做的。cdn

  1. 不容许read和load、store和write单一出现,由于他们是一个连贯的操做。并且必须是按顺序执行的。load必须是read以后,write必须是store以后,可是不必定是连续操做,在他们之间能够插入其余的指令。
  2. 不容许线程丢弃assign操做,也就是说执行完了以后必须放入工做内存中。
  3. 不容许线程不通过Assign操做直接把数据给主内存。
  4. 一个新的变量只能在主内存中诞生。
  5. 一个变量只容许一个线程对其lack操做,可是能够被一个线程lack屡次,lack屡次以后只有执行相同次数的unlack才能被解锁。
  6. 若是一个变量执行了lack操做以后将会清楚工做内存中该变量的值。执行引擎在使用变量时须要从新执行read-load-use等操做。
  7. 若是没有执行一个lack操做的变量不能执行unlack操做。或者被其余线程执行了lack操做的线程也不能被改线程执行unlack。
多线程并发的优点和缺点

相关文章
相关标签/搜索