解决重排序引起的有序性
在之前的章节中,我们分析到基于执行效率的考虑,代码会被重排序
重排序会经历如下几个过程
当然重排序也不是随便排,需要遵循as-if-serial原则,即不管怎么重排序(单线程)程序的执行结果不能被改变。编译器,runtime和处理器都必须遵循as-if-serial语义
例如如下一段代码
double pi = 3.14 // a
double r = 1.0 // b
double area = pi * r * r // c
数据之间的依赖关系如下所示
A和B之间并没有依赖关系,因此可以执行重排序
重排序除了需要遵循as-if-serial原则(用于单个线程),Java还定义了happens-before原则也需要遵循(用于单个线程和多个线程)
两个操作之间具有happens-before关系,并不意味着前一个操作必须妖在后一个操作之前执行!happens-before仅仅要求前一个操作(执行的结果)对后一个操作可见,且前一个操作按顺序排在第二个操作之前
如下为java中定义的8种happens-before原则
- 一个线程中的每个操作happens-before于该线程中的任意后续操作
- 对一个锁的解锁,happens-before于随后对这个锁的加锁
- 对一个volatile域的写,happens-before于任意后续对这个volatile域的读
- 如果A hapens-before B,且B happens-before C, 那么A happens-before C
- 如果线程A执行操作ThreadB.start()(启动线程B),那么A线程的ThreadB.start()操作happens-before 于线程B中的任意操作
- 如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before 于线程A从ThreadB.join()操作成功返回
- 对线程interrup()方法的调用, happens-before 于被中断线程的代码检测到中断事件的发生
- 一个对象的初始化完成(构造函数执行结束)happens-before finalize()方法的开始
解决缓存引起的可见性
在Java中无论是锁还是volatile等,都是通过内存屏障来实现可见性的。
在Java中有如下四种屏障类型
屏障类型 | 指令示例 | 说明 |
---|---|---|
LoadLoad Barriers | Load1; LoadLoad; Load2 | 确保Load1数据的装载先于Load2及所有后续装载指令的装载 |
StoreStore Barriers | Store1; StoreStore; Store2 | 确保Store1数据对其他处理器可见(刷新到内存)先于Store2及所有后续存储指令的存储 |
LoadStore Barriers | Load1; LoadStore; Store2 | 确保Load1数据装载优先于Store2及所有后续的存储指令刷新到内存 |
StoreLoad Barriers | Store1; StoreLoad; Load2 | 确保Store1数据对其他处理器变得可见(只刷新到内存)先于Load2及所有后续装载指令的装载 |
内存屏障主要有如下两个作用
- 阻止屏障两侧的指令重排序
- 强制将内存中的数据写回主内存
对于Load Barrier来说,在指令前插入Load Barrier,可以让高速缓存中的数据失效,强制从新从主内存加载数据
对于Store Barrier来说,在指令后插入Store Barrier,能让写入缓存中的最新数据更新写入主内存,让其他线程可见
参考博客
[1]https://www.cnblogs.com/wenjieyatou/p/6210189.html
[2]https://www.jianshu.com/p/1508eedba54d/