Java多线程环境下可能会出现指令重排的代码示例
什么是指令重排?
我们在执行程序时,为了提高性能,编译器和处理器某些情况下会对指令进行重排序。
as-if-serial语义
不管怎么重排序,不能影响单线程环境下的执行结果,这是as-if-serial语义定义的,编译器和处理器阶段的重排都遵循该规则。
编译器和处理器都遵循的指令重排的原则
a、数据依赖,只对不存在数据依赖的指令进行重排。
b、控制依赖,允许对有控制依赖关系的指令做重排。
Java多线程环境下可能会出现指令重排的代码示例
既然有了上面两个指令重排的前提,我们就可以根据这些规则写一个demo来实际测验出指令重排。
这个示例里,两个线程无论谁先执行,m和n都不可能都为0,如果出现了,就代表发生了指令重排
public class SysParamEnumController { int a = 0; int b = 0; int m = 0; int n = 0; public static void main(String[] args) throws InterruptedException { SysParamEnumController sysParamEnumController = new SysParamEnumController(); while (true){ Thread t1 = new Thread(() -> { sysParamEnumController.a = 1; sysParamEnumController.m = sysParamEnumController.b; }); Thread t2 = new Thread(() -> { sysParamEnumController.b = 2; sysParamEnumController.n = sysParamEnumController.a; }); t1.start(); t2.start(); t1.join(); t2.join(); if (sysParamEnumController.m == 0 && sysParamEnumController.n == 0) { System.out.println("m和n都为0,代表出现了指令重排"); break; } sysParamEnumController.a = 0; sysParamEnumController.b = 0; sysParamEnumController.m = 0; sysParamEnumController.n = 0; } } }
结果
循环结束就代表受到了指令重排的影响,因为指令重排只会小概率出现,所以每次跑的时间都会不一样。
如何避免指令重排?
使用内存屏障(volatile)或临界区(synchronized)
对应代码实现分别是给a和b加上volatile
volatile int a = 0; volatile int b = 0;
volatile的规则
1、第一个操作是volatile读时,第二个无论什么操作都不能重排
2、第二个操作是volatile写时,第一个无论什么操作都不能重排
3、第一个操作是volatile写时,第二个是volatile读时不能重排
或者给两个线程里的逻辑加上synchronized