在多核处理器的环境下:编译器可能改变两个操作的先后顺序;处理器可能不是完全依照程序的目标代码所指定的顺序执行命令;一个处理器执行的多个操作,在其他处理器的角度来看,其顺序可能与目标代码所指定的顺序不一致。这种现象就叫重排序。
重排序类型 | 含义 |
---|---|
LoadLoad重排序 | 该重排序指一个处理器上先后执行两个读内存操作L1和L2,其他处理器对这两个内存操作的感知顺序可能是L2——>L1,即L1被重排序到L2之后。 |
StoreStore重排序 | 该重排序指一个处理器上先后执行两个写内存操作W1和W2,其他处理器对这两个内存操作的感知顺序可能是W2——>W1,即W1被重排序到W2之后。 |
LoadStore重排序 | 该重排序指一个处理器上先后执行读内存操作L1和写内存操作W2,其他处理器对这两个内存操作的感知顺序可能是W2——>L1,即L1被重排序到W2之后。 |
StoreLoad重排序 | 该重排序指一个处理器上先后执行写内存操作W1和读内存操作L2,其他处理器对这两个内存操作的感知顺序可能是L2——>W1,即W1被重排序到L2之后。 |
内存重排序与具体的处理器微架构有关,基于不同微架构的处理器所允许的内存重排序是不同的,这里不再阐述。
对于编译器,JMM的编译器重排序规则会禁止特定类型的编译器重排序
对于处理器重排序,JMM的处理器重排序规则会要求Java编译器在生成指令序列时,插入特定类型的内存屏障指令,通过内存屏障指令来禁止特定类型的处理器重排序。
数据依赖性: 如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性。数据依赖分为下列3种类型: 1.写后读:a=1;b=a; 2.写后写:a=1;a=2; 3.读后写:a=b;b=1;
为了遵守as-if-serial语义,编译器和处理器在重排序时,会遵守数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序。因为这种重排序会改变执行结果。
不同处理器之间和不同线程之间的数据依赖性不被编译器和处理器考虑。
在单线程程序中,对存在控制依赖的操作重排序,不会改变执行结果(这也是as-if-serial语义允许对存在控制依赖的操作做重排序的原因);但在多线程程序中,对存在控制依赖的操作重排序,可能会改变程序的执行结果。
当操作1和操作2重排序时
当操作3和操作4重排序时
重排序在这里破坏了多线程程序的语义!
通过加锁同步可解决该问题
参考资料:
1.Java并发编程的艺术(方腾飞 魏鹏 程晓明 著)
2.Java多线程编程实战指南(黄文海 著)