jmm       

java内存模型JMM

模型的抽象

image-20191030102359219

jmm通过控制主内存与每个线程的本地内存之间的交互,来为java程序员提供内存可见性保证

很多处理器内存模型允许读/写重排序,所以jmm展示一个一致的内存模型,在重排序的地方插入内存屏障,禁止处理器重排序。所以称JMM是语言级的内存模型

image-20191030163910667

只要多线程程序是正确同步的,jmm保证该程序在任意处理器平台上的执行结果,与该程序在顺序一致性内存模型中的执行结果一致

对会改变程序执行结果的重排序,jmm会要求编译器和处理器禁止这种重排序。

对于不会改变程序执行结果的重排序,jmm对编译器和处理器不做要求

屏蔽了各种硬件和操作系统对内存访问的差异性(多线程情况下会有编译器优化和指令优化),从而实现让java程序在各种平台下都能达到一致的并发效果。

重排序

三种排序

image-20191030104912982

1属于编译器重排序,2、3属于处理器重排序,这些重排序会导致多线程程序出现内存可见性问题

jmm编译器重排序规则禁止特定类型的编译器重排序,处理器重排序规则会要求java编译器生成指令的时候,插入内存屏障指令来禁止处理器重排序。

image-20191030110338501

happens-before

如果想要一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系,否则重排序会导致一个操作对另一个操作不可见

程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作

监视器锁规则:解锁happens-before对同一个锁的加锁

volatile变量规则:对一个volatile域写,happens-before后续对这个volatile域的读

传递性:a happens-before b,b happens-before c,则a happens-before c

happens-before仅仅要求前一个操作对后一个操作可见,且前一个操作按顺序排在第二个操作前面,但不一定前一个操作先于后一个操作执行,因为某些情况允许重排

补充:

程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。
监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。
volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。 
传递性:如果A happens-before B,且B happens-before C,那么A happens-before C。
start()规则:如果线程A执行操作ThreadB.start()(启动线程B),那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作。
join()规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。
程序中断规则:对线程interrupted()方法的调用先行于被中断线程的代码检测到中断时间的发生。 
对象finalize规则:一个对象的初始化完成(构造函数执行结束)先行于发生它的finalize()方法的开始。

image-20191030164501582

volatile

  1. 写一个volatile变量,jmm会把该线程对应的本地内存中的共享变量值刷新到主内存
  2. 读一个volatile变量,jmm会把该线程对应的本地内存置为无效,从主内存都去共享变量

image-20191030172335532

实现方式:

  1. volatile写之前的操作不会被重排到volatile写之后

  2. volatile读之后的操作不会被重排到volatile读之前

  3. 第一个操作是volatile写,第二个操作是volatile读时,不能重排序

volatile写前面会插入一个StoreStore屏障,后面会插入StoreLoad屏障

volatile读后面会插入loadload屏障和loadstore屏障

普通读写可以重排在后面,volatile读写不能重排在后面
		|
		|
volatile读
		|		
		|
普通读写不能重排到上面,volatile读写不能重排在上面



普通读写不能重排到后面,volatile读写不能重排在后面
		|
		|
volatile写
		|
		|
普通读写可以重排到上面,volatile读写不能重排在上面

image-20191030175428563

image-20191030175452455

利用了volatile来保证解锁happens-before对同一个锁的加锁,锁释放与volatile写有相同语义,锁获取与volatile读有相同的内存语义

final

  1. 在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,则这两个操作之间不能重排序

  2. 初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序

reference

https://www.infoq.cn/article/java_memory_model深入理解java内存模型