Java极致并发:(五)reordering与volatile

Apr 21, 2016   #并发  #Java 
第一篇中提到volatile有讲到,JMM(Java Memory Model)定义了happens-before关系来保证对volatile变量读写的可见性约束。这种happens-before的关系需要编译器和处理器一起维护。本文简述了一些Java compiler如何保证happens-before语义的一些方法,详细内容可以参考JMM-COOKBOOK。关于内存屏障的详细解释,请参考Memory Barriers: a Hardware View for Software Hackers

一、编译器Reordering

我们知道编译器会对我们的代码进行优化,只需要保证as-if-sequential的一致性即可。reordering(重排)即是其中的一种优化方法,其通过重排内存读写顺序用以优化程序性能。

在多线程环境中,重排读写指令会带来一些问题。如,将synchronized内部的读指令,重排到synchronized外部,显然是一个错误,应为synchronized内部的读指令有可见性保证,而重排后没有了可见性保证,可能会读取到旧的值。

也就是说,读写指令的重排可能违背JMM中happens-before的约束。因此,某些操作是不能重排的。根据JMM中关于happens-before关系的定义,可以得到下面的表格。

Can Reorder2nd operation
1st operationNormal Load
Normal Store
Volatile Load 
MonitorEnter
Volatile Store 
MonitorExit
Normal Load
Normal Store


No
Volatile Load 
MonitorEnter
No
No
No
Volatile store 
MonitorExit

No
No

二、CPU Reordering

出了编译器会重排优化指令外,CPU也会乱序执行指令,只需要保证as-if-sequential的一致性。在多线程环境下,CPU提供了一些内存屏障指令,来禁止这种重排,从而维持happens-before关系。
  1. LoadLoad屏障:Load1; LoadLoad; Load2,保证Load1先于Load2以及其后所有Load指令加载。
  2. StoreStore屏障:Store1; StoreStore; Store2,保证Store1先于Store2以及其后所有Store指令,被flush。
  3. LoadStore屏障:Load1; LoadStore; Store2,保证Load1在Store2以及其后所有Store指令flush之前加载。
  4. StoreLoad屏障:Store1; StoreLoad; Load2,保证Store1先于Load2以及其后所有Load指令,被flush。
被flush即写入主存,将被所有处理器可见。

从语义上来看,LoadLoad、LoadStore操作应该比较廉价,StoreStore和StoreLoad操作比较昂贵(涉及到写主存以及高速缓存协商等)。

Required barriers2nd operation
1st operationNormal LoadNormal StoreVolatile Load 
MonitorEnter
Volatile Store 
MonitorExit
Normal Load


LoadStore
Normal Store


StoreStore
Volatile Load 
MonitorEnter
LoadLoadLoadStoreLoadLoadLoadStore
Volatile Store 
MonitorExit


StoreLoadStoreStore

上表即编译器应该在相关操作中间插入的屏障指令。

三、volatile的读写性能

从前面的讨论可以看到,volatile的写操作比读操作要昂贵许多。本文通过使用JMH来测试volatile读写与非volatile的性能差距。测试结果如下

BenchmarkModeCntScoreErrorUnits
VolatileLoadStoreBenchmark.measureNormalLoadavgt102.324± 0.014ns/op
VolatileLoadStoreBenchmark.measureNormalStoreavgt100.386± 0.003ns/op
VolatileLoadStoreBenchmark.measureVolatileLoadavgt102.340± 0.088ns/op
VolatileLoadStoreBenchmark.measureVolatileStoreavgt105.815± 0.013
ns/op
测试机器为Intel Core i7-4710MQ。可以看到volatile store的性能要比nomal store低近一个数量级。

相关代码在此处:Github

下一篇文章,将会对synchronized、Lock、CAS等同步工具的性能给出测试。