首页 > 解决方案 > 不稳定的 Java 重新排序

问题描述

首先让我说,我知道这是一个相当普遍的话题,但在搜索它时,我找不到另一个可以澄清以下情况的问题。如果这可能是重复的,我很抱歉,但是你去:

我是并发新手,为了回答问题,我得到了以下代码:

 boolean flag = false;

    void changeVal(int val) {
        if(this.flag){
            return;
        }
        this.initialInt = val;
        this.flag = true;
    }

    int initialInt = 1;

    class MyThread extends Thread {
        public void run(){
            changeVal(0);
            System.out.print(initialInt);
        }
    }

    void execute() throws Exception{
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        t1.start(); t2.start(); t1.join(); t2.join();
        System.out.println();
    }

对于a ) 我的回答如下:在没有任何 volatile / 同步构造的情况下,编译器可以重新排序一些指令。特别是“this.initialInt = val;” 和“this.flag = true;” 可以切换,以便发生这种情况:线程都已启动并且 t1 提前收费。给定重新排序的指令,它首先设置 flag = true。现在在它到达“this.initialInt = val;”的最后一条语句之前 另一个线程跳进来,检查 if 条件并立即返回,从而打印未更改的 initialInt 值 1。除此之外,我相信如果没有任何 volatile / 同步,不确定 t2 是否会看到在 t1 中对 initialInt 执行的分配所以它也可以打印“1”作为默认值。

对于b)我认为该标志可能会变得不稳定。我了解到,当 t1 写入 volatile 变量设置 flag = true 然后 t2 时,在 if 语句中读取此 volatile 变量时,将看到在 volatile 写入之前执行的任何写操作,因此 initialInt = val 也是。因此,t2 已经看到它的 initialInt 值更改为 0,并且必须始终打印 0。但是,如果使用 volatile 成功阻止了我在 a) 中描述的任何重新排序,这只会起作用。我已经阅读过有关 volatile 完成此类事情的信息,但我不确定在没有任何进一步同步块或任何此类锁的情况下这是否总是有效。从这个答案我已经收集到,在 volatile 存储(因此 this.flag = true)可以重新排序以使其超出它之前没有发生任何事情。在那种情况下,initialInt = val 不能向下移动,我应该是正确的,对吧?或不 ?:)

非常感谢你的帮助。我期待着您的回复。

标签: javaconcurrencyvolatilehappens-before

解决方案


没有显式同步,因此各种交错都是可能的,并且一个线程所做的更改不一定对另一个线程可见,因此,更改 to 可能在更改flag之前可见initialInt,导致 10 或 01 输出,如以及00输出。11 是不可能的,因为对变量执行的操作对于执行它们的线程是可见的,并且changeVal(0)对于至少一个线程来说,效果总是可见的。

使changeVal同步或使flagvolatile 可以解决问题。flag是关键部分中最后一个更改的变量,因此将其声明为volatile会创建发生之前的关系,从而使更改initialInt可见。


推荐阅读