多线程并发-计算机基础

CPU缓存一致性协议MESI

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

  1. 时间局部性(Temporal Locality):如果一个信息项正在被访问,那么在近期它很可能还会被再次访问。

比如循环、递归、方法的反复调用等。

  1. 空间局部性(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来维护大小,优点是可以动态的确定大小,缺点是运行时动态确定内存所以速度相对栈小一点。对象存放在堆上。静态变量跟随类一起存放在堆上。

  1. 栈stack

栈内存的速度相对堆内存更快,仅次于寄存器,缺点是大小必须是编译期确定的。缺乏一定的灵活性,存放一些基本的数据变量(int double。。。)java内存要求本地变量(Local Variable),调用栈必须存放在线程栈(Thead Stack)中。

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

硬件内存模型如图

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


java内存抽象模型结构


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

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

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

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

  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。
多线程并发的优势和缺点

你可能感兴趣的:(java,多线程,计算机基础)