重排序保证对依赖关系执行结果一致的理解

Java 并发编程(1): Java 内存模型(JMM)

as-if-serial 语义

无论如何重排序, 单线程程序的执行结果不能被改变.
为了遵守该语义, 编译器和CPU 不会对存在数据依赖关系的操作做重排序.
造成了一个幻觉: 单线程程序是按程序的顺序来执行的.

数据依赖性

如果两个操作访问同一变量, 且有一个是写操作, 则这两个操作存在数据依赖性.
它仅针对单个CPU 中执行的指令序列和单个线程中执行的操作.

从JAVA 源代码到最终实际执行的指令序列, 会经历3种重排序:

1,编译器优化重排序: 在不改变单线程程序语义的前提下, 重新安排语句的执行顺序.
2,指令级并行的重排序: 如果不存在数据依赖性, CPU 可以改变语句对应机器指令的执行顺序. 采用ILP(指令级并行技术) 来将多条指令重叠执行.
3,内存系统的重排序: 由于CPU 私用缓存和读
/写缓冲区, 加载和存储操作看起来是在乱序执行.

1 属于编译器重排序, 2和3 属于处理器重排序.

JMM 的设计初衷

程序员希望内存模型易于理解和编程, 所以需要一个强内存模型.
编译器和CPU 则希望内存模型对其有最小的束缚, 方便做优化来提高性能. 所以需要一个弱内存模型.
重排序会导致多线程程序出现内存可见性问题.

JMM 的编译器重排序规则会禁止特定类型的重排序.
JMM 的处理器重排序规则会在生成指令序列时, 插入特定类型的内存屏障来禁止特定类型的重排序.

JMM 属于语言级的内存模型. 它确保在不同的编译器和处理器平台上, 通过禁止特定类型的编译器和处理器重排序, 对外提供一致的内存可见性保证.

控制依赖关系

前序操作是条件语句(if, while...), 则后续操作和前序之间就产生了控制依赖关系.
当代码中存在控制依赖性时, 会影响指令序列执行的并行度.

采用猜测(Speculation) 执行来克服控制相关性对并行度的影响.
例如, CPU 会执行后续操作,并将其计算结果保存到重排序缓存(Reorder Buffer: ROB) 的硬件缓存中,如果条件为真, 直接使用该结果顺序执行.
在多线程中, 对存在控制依赖的操作重排序, 可能会改变程序的执行结果.

 

总结:主要存在两种依赖

1,数据依赖,单线程,相关执行不会重排序。
2,控制依赖,单线程,可能会重排序(条件满足),先执行再判断条件;条件不满足的情况,虽然重排序,但执行结果在缓存中,相当于没有重排序。

特别注意,多线程情况下,条件是否满足,是难以确认的。得看多个线程的条件赋值和判断的执行时机。

 

posted @ 2018-03-22 00:03  假程序猿  阅读(936)  评论(0)    收藏  举报