指令是指示计算机执行某种操作的命令,如:数据传送指令、算术运算指令、位运算指令、程序流程控制指令、串操作指令、处理器控制指令。指令不同于我们所写的代码,一行代码按照操作的逻辑可以分成多条指令。
举个例子:int a = 1; 这段代码大致可以分为两条指令:1.加载常量1;2.将常量1赋值给变量a。
只要程序的最终结果与它顺序化情况的结果相等,那么指令的执行顺序可以与代码逻辑顺序不一致,这个过程就叫做指令的重排序。
指令重排序的意义:使指令更加符合 CPU 的执行特性,最大限度的发挥机器的性能,提高程序的执行效率。
指令重排序主要分为三种,在这里主要讨论 JVM 中的指令重排序。
- 编译器重排序:JVM 中完成
- 指令级并行重排序
- 处理器重排序:CPU 中完成
如果程序中操作A在操作B之前,那么线程中操作A将在操作B之前执行。(只对指令内部重排序,不在指令间重排序)
- As-If-Serial语义
不管怎么进行指令重排序,单线程内程序的执行结果不能被改变。
编译器和处理器对存在依赖关系的操作都不会对其进行重排序。只有不存在依赖关系的操作有可能进行重排序。
Happens-Before原则
保证正确同步的多线程程序的执行结果不被改变。
对于被同步的操作,如果操作 A 先于操作 B,那么 A 操作的执行结果将对 B 操作可见,而且 A 操作的执行顺序排在 B 操作之前。
管理锁定规则:一个unlock操作happen—before后面(时间上的先后顺序)对同一个锁的lock操作。 (如果线程1解锁了monitor a,接着线程2锁定了a,那么,线程1解锁a之前的写操作都对线程2可见(线程1和线程2可以是同一个线程))
volatile关键字通过“内存屏障”来防止指令被重排序。
为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。
Java内存模型采取保守策略(见缝就插)
在每个volatile写操作的前面插入一个StoreStore屏障。 在每个volatile写操作的后面插入一个StoreLoad屏障。 在每个volatile读操作的后面插入一个LoadLoad屏障。 在每个volatile读操作的后面插入一个LoadStore屏障。
Synchronized 把多线程执行环境改变为单线程执行环境,无需关心指令重排序(单线程执行结果不会改变)。