Java内存模型(MESI、内存屏障、volatile和锁及final内存语义)

JMM (Java内存模型)

Java线程的实现

实现线程主要有三种方式,Java线程从JDK1.3后采用第一种方式实现:

  1. 使用内核线程实现(1:1实现)
  2. 使用用户线程实现(1:N实现)
  3. 使用用户线程加轻量级进程混合实现(N:M实现)



  • KTL: 内核线程
  • LWP:轻量级进程
  • UT:用户线程
线程之间通信机制

Java并发采用的是共享内存模型

  1. 共享内存
  2. 消息传递
重排序

包括:

  1. 编译器优化的重排序。
  2. 指令级并行的重排序。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
  3. 内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行
内存屏障

内存屏障类型:

StoreLoad Barries的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能

缓存一致性(MESI协议)

//en.wikipedia.org/wiki/MESI_protocol#Memory_Barriers

状态:

  • 修改(modified):仅当前处理器拥有该缓存行,并且缓存行被修改过了,一定时间内会写回主存,会写成功状态会变为S。
  • 独占(exclusive):仅当前处理器拥有该缓存行,并且没有修改过,是最新的值。
  • 共享(share):有多个处理器拥有该缓存行,每个处理器都没有修改过缓存,是最新的值。
  • 失效(invalid):缓存行被其他处理器修改过,该值不是最新的值,需要读取主存上最新的值。
MESI M E S I
M X X X
E X X X
S X X
I

这个图的含义就是当一个core持有一个cacheline的状态为Y时,其它core对应的cacheline应该处于状态X, 比如地址 0x00010000 对应的cacheline在core0上为状态M, 则其它所有的core对应于0x00010000的cacheline都必须为I

状态转换:

  • Red: Bus initiated transaction.
  • Black: Processor initiated transactions.

处理器请求:

  • PrRd: 处理器请求读Cache block
  • PrWr: 处理器请求写Cache block

总线请求:

  • BusRd:由一个处理器向另一个处理器发出的缓存读请求
  • BusRdX:由一个缓存中没有该变量的处理器向另一个处理器发送的缓存写请求
  • BusUpgr:由一个缓存中拥有该变量的处理器向另一个处理器发送的缓存写请求
  • Flush:有一个处理器将变量写回了主存
  • FlushOpt:通知别的处理器修改变量的值(缓存间传值)


示例:

Happens-Before
  • 某个线程中的每个动作都happens-before该线程中该动作后面的动作
  • 某个管程上的unlock动作happens-before同一个管程上后续的lock动作
  • 对某个volatile字段的写操作happens-before每个后续对该volatile字段的读操作
  • 在某个线程对象上调用start()方法happens-before该启动了的线程中的任意动作
  • 某个线程中的所有动作happens-before任意其它线程成功从该线程对象上join()中返回
  • 如果某个动作a happens-before动作b,且b happens-before动作c,则有a happens-before c
volatile字段内存语义

happens-before规则

对某个volatile字段的写操作happens-before每个后续对该volatile字段的读操作

特性:

  • 可见性: 对任意单个volatile变量的读,总是能看到(任意线程)对该volatile最后的写入
  • 原子性: 对任意单个volatile变量的读/写具有原子性,但类似与volatile++这种复合操作不具有原子性

内存语义

当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存
内存语义的实现:volatile修饰的变量,会多一个lock指令,这个操作的作用相当于一个内存屏障。
保守策略的JMM内存屏障插入策略:

  • 在每个volatile写操作的前面插入一个StoreStore屏障
  • 在每个volatile写操作的后面插入一个StoreLoad屏障
  • 在每个volatile读操作的后面插入一个LoadLoad屏障
  • 在每个volatile读操作的后面插入一个LoadStore屏障

锁的内存语义

happens-before规则

某个管程上的unlock动作happens-before同一个管程上后续的lock动作

内存语义

当线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中。

ReentrantLock

CAS实现
synchronized
ACC_SYNCHRONIZED方法访问标识或monitor enter、monitor exit指令实现

final字段内存语义

两个排序规则:

  1. 在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序
  • JMM禁止编译器把final域的写重排序到构造函数之外
  • 编译器会在final域的写之后,构造函数return之前,插入一个storestore屏障,这个屏障禁止处理器把final域的写重排序到构造函数之外。
  1. 初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序
JSR-133对旧内存模型的修补
  1. 增强volatile的内存语义。旧内存模型运行volatile变量与普通变量重排序。JSR-133严格限制volatile变量与普通变量的重排序。
  2. 增强final的内存语义。在旧内存模型,多次读取同一个final变量的值可能会不同。为此,JSR-133为final增加了两个重排序规则,在保证fianl引用不会从构造函数内逃逸出的情况下,final具有了初始化安全性。

参考: