正所谓耳听为虚,但是眼见也不一定为实。
在java底层的一些运行机制上便是如此。
关于指令重排序
我一句话的理解就是:处理器和编译器为了提高并行度和系统性能,会将没有相互依赖的指令进行重新排序然后再执行。
其实就是说,不管你代码写的再乱,逻辑再不清晰,我jvm也得找一条我认为舒服的方式来运行你的代码
jvm认为没有相互依赖的指令换换位置也没啥影响吗,更何况我认为这样执行代码的姿势更为舒服,何乐而无为呢。
当然这是站在单个线程的角度来考虑这个问题的,就像一个人睡一张床再怎么变换姿势又不会有啥影响,但是多线程
情况下操作的是同一个内存模型的话,是很有可能影响到另一个线程的。
姑且看下面这个例子。
public class Test {
public int a = 0;
public boolean flag = false;
public int i;
public void writer() {
a = 1; //指令1
flag = true; //指令2
}
public void reader() {
if(flag) { //指令3
i = a * a; //指令4
}
}
public static void main(String[] args) {
Test test = new Test();
new Thread(){
public void run() {
test.writer();
}
}.start();
new Thread(){
public void run() {
test.reader();
}
}.start();
try {
Thread.sleep(1000);
System.out.println(test.flag+":"+test.i+":"+test.a);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
一个线程操作的是1和2指令,另一个线程操作的是其他指令。
正常情况下输出结果
true:1:1
如果第一个线程变换了姿势,先执行指令2然后让第二线程执行指令3和4那么结果就会是这样
true:0:1
当然还有一方面,指令3和指令4具有相互依赖性,因此本身并不能进行指令重排序,但是处理器和编译器为了保证序列执行的并行度,会进行预测执行,即先计算出a*a的值并且将改值存入到一个叫重排序缓存的硬件缓存中,当flag满足条件时再将改值赋给后来定义的i,这样也能出现0:true的结果。
这就是一个简单的指令重排序的问题。
哦,对了,我记得加上volatile就可以固定好你们的姿势
public volatile int a = 0;
public volatile boolean flag = false;
这样你们就可以老老实实了。
参考资料
java并发变成的艺术 ----方腾飞著