并发编程--线程安全性

安全性

Volatile

保证共享变量可见性,实现跨线程写入的可见性 。
在修改带有 volatile 修饰的成员变量时,会多一个lock 指令。lock是一种控制指令,在多处理器环境下,lock汇编指令能够基于总线锁或者缓存锁的机制来达到可见性的一个效果。缓存

CPU高速缓存

L1,L2,L3 3级高速缓存安全

缓存一致性

高速缓存的存在之后,每一个 CPU 的处理过程是,先将计算须要用到的数据缓存在 CPU 高速缓存中,在 CPU进行计算时,直接从高速缓存中读取数据而且在计算完成以后写入到缓存中。在整个运算过程完成后,再把缓存中的数据同步到主内存。因为在多CPU中,每一个线程可能会运行在不一样的CPU内,而且每一个线程拥有本身的高速缓存。同一份数据可能会被缓存到多个CPU中,若是在不一样CPU中运行的不一样线程看到同一分内存的缓存值不同就会存在缓存不一致的问题 为了解决缓存不一致的问题,主要提供了两种解决办法多线程

  1. 总线锁
  2. 缓存锁 总线锁和缓存锁
    总线锁,简单来讲就是,在多cpu下,当其中一个处理器要对共享内存进行操做的时候,在总线上发出一个LOCK#信号,这个信号使得其余处理器没法经过总线来访问到共享内存中的数据,总线锁定把CPU和内存之间的通讯锁住了,这使得锁按期间,其余处理器不能操做其余内存地址的数据,因此总线锁定的开销比较大,这种机制显然是不合适的。
    如何优化呢?
    最好的方法就是控制锁的保护粒度,咱们只须要保证对于被多个CPU缓存的同一份数据是一致的就行。因此引入了缓存锁,它核心机制是基于缓存一致性协 议来实现的。

缓存一致性协议

常见的协议有MSI,MESI,MOSI 等。最多见的就是 MESI 协议。app

MESI

MESI 表示缓存行的四种状态,分别是异步

  1. M(Modify) 表示共享数据只缓存在当前CPU缓存中,而且是被修改状态,也就是缓存的数据和主内存中的数据不一致
  2. E(Exclusive) 表示缓存的独占状态,数据只缓存在当前CPU 缓存中,而且没有被修改
  3. S(Shared) 表示数据可能被多个CPU缓存,而且各个缓存中的数据和主内存数据一致
  4. I(Invalid) 表示缓存已经失效storebuffer 异步执行致使CPU的乱序执行->重排序
    在 MESI 协议中,每一个缓存的缓存控制器不只知道本身的读写操做,并且也监听(snoop)其它 Cache 的读写操做
    对于 MESI 协议,从 CPU 读写角度来讲会遵循如下原则:
  • CPU 读请求:缓存处于 M、E、S 状态均可以被读取,I 状 态 CPU 只能从主存中读取数据
  • CPU 写请求:缓存处于 M、E 状态才能够被写。对于 S 状态的写,须要将其余 CPU 中缓存行置为无效才可写使用总线锁和缓存锁机制以后,CPU 对于内存的操做大概能够抽象成下面这样的结构。从而达到缓存一致性效果

MESI优化带来的可见性问题

MESI 协议虽然能够实现缓存的一致性,可是也会存在一些问题。就是各个 CPU 缓存行的状态是经过消息传递来进行的。如 果 CPU0 要对一个在缓存中共享的变量进行写入,首先须要发送一个失效的消息给到其余缓存了该数据的 CPU。而且要等到他们的确认回执。CPU0 在这段时间内都会处于阻塞状态。为了不阻塞带来的资源浪费。

在 cpu 中引入了 Store Bufferes。
CPU0 只须要在写入共享数据时,直接把数据写入到storebufferes中,同时发送invalidate消息,而后继续去处理其余指令。当收到其余全部CPU发送了 invalidate acknowledge消息时,再将 store bufferes 中的数据数据存储至cacheline中。最后再从缓存行同步到主内存。

存在的问题:
exeToCPU0和exeToCPU1分别在两个独立的CPU上执行。假如 CPU0 的缓存行中缓存了 isFinish 这个共享变量,而且状态为(E)、而 Value 多是(S)状态。 那么这个时候,CPU0 在执行的时候,会先把 value=10 的指令写入到storebuffer中。而且通知给其余缓存了该value变量的 CPU。在等待其余 CPU 通知结果的时候,CPU0 会继续执行 isFinish=true 这个指令。而由于当前 CPU0 缓存了 isFinish 而且是 Exclusive 状态,因此能够直接修改 isFinish=true。这个时候 CPU1 发起 read操做去读取 isFinish 的值可能为 true,可是 value 的值不等于 10。
storebuffer实际上是一个异步操做,出现了缓存指令的重排序,CPU的乱序执行。oop

内存屏障

Store Memory Barrier(写屏障) 告诉处理器在写屏障以前的全部已经存储在存储缓存(store bufferes)中的数据同步到主内存,简单来讲就是使得写屏障以前的指令的结果对屏障以后的读或者写是可见的 Load Memory Barrier(读屏障) 处理器在读屏障以后的读操做,都在读屏障以后执行。配合写屏障,使得写屏障以前的内存更新对于读屏障以后的读操做是可见的 Full Memory Barrier(全屏障) 确保屏障前的内存读写操做的结果提交到内存以后,再执行屏障后的读写操做。
内存屏障的做用能够经过防止 CPU 对内存的乱序访问来保证共享数据在多线程并行执行下的可见性。优化

volatile->lock(缓存锁) -> 内存屏障->可见性

JMM的内存模型

JMM 全称是 Java Memory Model。JMM实际上就是提供了合理的禁用缓存以及禁止重排序的方法。因此它最核心的价值在于解决可见性和有序性。
语言级别抽象内存模型->volatile、synchronized、final、(happens-before) 重排序 ->数据依赖规则
JMM内存屏障
Happens-Before 规则 可见性的保障线程

  1. 程序的顺序规则
  2. volatile规则
  3. 传递性
  4. start规则
  5. join规则
  6. synchronized监视器规则
相关文章
相关标签/搜索