1. 并发概念学习与准备

本章内容:

  1.并发与高并发理解

  2.缓存一致性

  3.乱序执行优化

  4.Java内存模型(JMM)

  5. 并发的优势、风险

 

一、并发与高并发理解

  并发:同时拥有两个或者多个线程,如果程序在单核处理器上运行,多个线程将交替地换入换出内存,这些线程时同时存在的,只不过处于执行过程中的不同状态。如果运行在多核处理器上,程序中每个线程都将拥有一个处理器核,因此可同时运行。多个线程操作相同的资源,保证线程安全,合理使用资源。

  高并发:通过设计保证系统能同时处理很多请求。服务同时处理很多请求,提高程序性能。

  

二、缓存一致性

  CPU的频率太快了,主存赶不上,这样在处理器时钟周期内,CPU常常等待主存,浪费资源。所以缓存的出现是为了环节CPU和内存之间速度不匹配的问题。

  时空局限性:如果某个数据被访问,那么在不久的将来可能再次被访问。

  空间局限性:如果某个数据被访问,那么与它相邻的数据很快也可能被访问。

  在多处理器的系统中(或者单处理器多核的系统),每个处理器内核都有自己的高速缓存,他们共享同一主内存,当多个处理器的运算任务都涉及同一块主内存区域时,将可能导致各自的缓存数据不一致。为此,需要各个处理器访问缓存时都遵循一些协议,在读写时根据协议操作,来维护缓存的一致性。

  【MESI协议】MESI协议用于保证多个CPU cache之间缓存共享数据的一致性。MESI分别是缓存行四种状态的首字母:

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

  MESI状态转换:

 

 

 

  假设cache 1 中有一个变量x = 0的cache line 处于S状态(共享)。那么其他拥有x变量的cache 2、cache 3等x的cache line调整为S状态(共享)或者调整为 I 状态(无效)。

 

三、乱序执行和内存屏障

  1.乱序执行

  乱序执行就是说把原来有序执行的指令列表,在保证执行结果一致的情况下,根据指令依赖关系和指令执行周期重新安排执行顺序。(只重结果,不看过程)

  乱序执行可以提高处理器内部逻辑元件的利用率以提高运行速度。现在普遍使用的一些超标量处理器通常能在一个指令周期内并发执行多条指令。处理器从L1 I-Cache预取了一批指令后,就会分析找出那些互相没有关系可以并发执行的指令,然后送到几个独立的执行单元并发执行。

  但是,在多核的情况下,由于内部的高速缓存,乱序执行对访问指令的影响可能导致数据的变化不能及时的反映在主存上,从而导致错误的结果。

  【编译器的乱序优化】

  处理器预取单元的能力有限,处理器一次只能分析一小块指令的并发性。但是编译器能够对一大块范围的代码进行分析,能够从更大的范围内分辨出可以并发的指令,并将其尽量靠近排列使得处理器更容易预取和并发执行。

  示例:


   编译器通常会优化掉前面一个对*p的写入(逻辑上冗余),仅对*p写入2。而对*q赋值的时候,编译器认为此时*q的结果就应该是上次*p的值,会优化掉从*p取数的过程,直接把在寄存器中保存的*p的值给*q

  2.内存屏障

  【读屏障 rmb()】处理器对读屏障前后的取数指令(LOAD)能保证有序,但是不一定能保证其他算术指令或者是写指令的有序。对于读指令的执行完成时间也不能保证,即它不能保证在屏障之前的读指令一定都执行完成,只能保证屏障之前的读指令一定能在屏障之后的读指令之前完成。

  【写屏障 wmb()】处理器对屏障前后的写指令(STORE)能保证有序,但是不一定能保证其他算术指令或者是读指令的有序。对于写指令的执行完成时间也不能保证,即它不能保证在屏障之前的写指令一定都执行完成,只能保证屏障之前的写指令一定能在屏障之后的写指令之前完成

  【通用内存屏障 mb()】处理器保障只有屏障之前的访存操作(包括读写)都完成以后才会执行屏障之后的访存操作。即可以保障读写之间的有序(但是同样无法保证指令完成的时 间)。这种屏障对处理器的执行单元效率产生的负面影响要比单纯用读屏障或者写屏障来的大

 

四、 Java内存模型(JMM)★★★★★

  Java内存区域和内存模型不是一种东西,内存区域是指Jvm运行时将数据分区域存储,强调对内存空间的划分。而Java内存模型(JMM)是定义了线程和主内存之间的抽象关系,即JMM定义了JVM在计算机内存中工作方式

  Java内存区域详解

  Java内存模型是共享内存的并发模型,线程之间主要通过读-写共享变量(堆内存中的实例域、静态域和数组元素)来完成隐式通信。JMM空直Java线程之间的通信,决定一个线程对共享变量的写入何时对另一个线程可见。

  【定义Java内存模型(Java Memory Model ,JMM)就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范。

  Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量(线程共享的变量)从内存存入取出这样的底层细节。JMM规定了所有的变量都存储在主内存中,每个线程还有自己的工作内存,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变。这里的工作内存是一个抽象概念,也叫本地内存,其存储了该线程读写共享变量的副本。

  不同线程之间无法直接访问对方工作内存中的变量,线程间的通信方式分为两种:通过消息传递、共享内存。Java线程之间的通信采用的是共享内存方式,线程、主内存和工作内存之间的交互关系如下图所示:

 

  物理机高速缓存和主内存之间的交互有协议,同样的,java内存中线程的工作内存和主内存的交互是由java虚拟机定义了如下的8种操作来完成的,每种操作必须是原子性的。java虚拟机中主内存和工作内存交互,就是一个变量如何从主内存传输到工作内存中,如何把修改后的变量从工作内存同步回主内存。

  • lock(锁定):作用于主内存的变量,一个变量在同一时间只能一个线程锁定,该操作表示这条线成独占这个变量
  • unlock(解锁):作用于主内存的变量,表示这个变量的状态由处于锁定状态被释放,这样其他线程才能对该变量进行锁定
  • read(读取):作用于主内存变量,表示把一个主内存变量的值传输到线程的工作内存,以便随后的load操作使用
  • load(载入):作用于线程的工作内存的变量,表示把read操作从主内存中读取的变量的值放到工作内存的变量副本中(副本是相对于主内存的变量而言的)
  • use(使用):作用于线程的工作内存中的变量,表示把工作内存中的一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时就会执行该操作
  • assign(赋值):作用于线程的工作内存的变量,表示把执行引擎返回的结果赋值给工作内存中的变量,每当虚拟机遇到一个给变量赋值的字节码指令时就会执行该操作
  • store(存储):作用于线程的工作内存中的变量,把工作内存中的一个变量的值传递给主内存,以便随后的write操作使用
  • write(写入):作用于主内存的变量,把store操作从工作内存中得到的变量的值放入主内存的变量中
  【重排序】在执行程序时为了提高性能,编译器核处理器常常会对指令做重排序。重排序分为三种类型:
  1.编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
  2.指令级并行的重排序。现代处理器采用了指令级并行技术来将多条指令重叠执行。如果数据之间没有关联,处理器可以改变语句对应机器指令的执行顺序。
  3.内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。
  在Java源代码到机器指令序列,会分别经历上述三种重排序:

   JMM属于语言级的内存模型,确保在不同的编译器和不同的处理器平台之上,通过禁止特定类型的编译器重排序和处理器重排序,为程序员提供一致的内存可见性保证。Java编译器禁止处理器重排序是通过在生成指令序列的适当位置会插入内存屏障指令来实现的。

  【 happens-before规则】Java内存模型通过happens-before规则来阐述操作之间的可见性。如果一个操作执行的结果需要对另一个操作可见,那么两个操作之间必须存在happens-before规则。如果 A happens-before B,那么Java内存模型将向程序猿保证——A操作的结果对B可见,并且A先于B执行。
  重要的happens-before规则如下:
  1.程序顺序规则:线程中的每个操作,happens-before与线程中的任意后序操作。
  2.监视器锁规则:对一个监视器锁的解锁,happens-before于对这个监视器的加锁。
  3.volatile变量规则:对一个volatile域的写,happens-before任意后续对这个域的读。
  4.传递性:A happens-before B,B happens-before C==》A happens-before C。
  5.线程启动规则:Thread对象start()方法先于任何动作。
  6. 线程中断规则:对线程interrupt()方法先行发生于被中断线程的代码检测到中断事件的发生。
  7.对象终结规则:一个对象的初始化先于finalize方法。
  8.线程终结规则:线程中所有操作先于终结检测。

 

   【volatile】volatile是一种JMM提供的轻量级同步机制,被volatile修饰的变量具有以下两种特性:

  1.保证此变量对所有线程的可见性。而普通变量在线程之间传递依靠主内存来实现。

  2.禁止指令重排序优化。

 

 五、并发的优势和风险

  优势:速度快、设计多、资源利用率高

  风险:安全性、活跃性(死锁、饥饿)、性能(CPU切换频繁、调度多、内存消耗多、同步机制)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

猜你喜欢

转载自www.cnblogs.com/qmillet/p/12078527.html