天天看点

java并发编程之指令重排序问题

正所谓耳听为虚,但是眼见也不一定为实。

在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并发变成的艺术 ----方腾飞著

继续阅读