易失性Java重新排序

易失性Java重新排序,java,concurrency,volatile,happens-before,Java,Concurrency,Volatile,Happens Before,首先让我说,我知道这是一个相当常见的话题,但在寻找它时,我找不到另一个问题来澄清以下情况。如果这是一个可能的副本,我非常抱歉,但给你: 我是并发新手,为了回答问题,我获得了以下代码: a) 为什么除了“00”之外还有其他输出 b) 如何修改代码,以便始终打印“00” 对于a)我的答案如下:在没有任何易失性/同步构造的情况下,编译器可以对一些指令重新排序。特别是,可以切换“this.initialInt=val;”和“this.flag=true;”,这样就可能出现这种情况:线程都已启动,t1

首先让我说,我知道这是一个相当常见的话题,但在寻找它时,我找不到另一个问题来澄清以下情况。如果这是一个可能的副本,我非常抱歉,但给你:

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

  • a) 为什么除了“00”之外还有其他输出
  • b) 如何修改代码,以便始终打印“00”
对于a)我的答案如下:在没有任何易失性/同步构造的情况下,编译器可以对一些指令重新排序。特别是,可以切换“this.initialInt=val;”和“this.flag=true;”,这样就可能出现这种情况:线程都已启动,t1已提前充电。给定重新排序的指令,它首先设置flag=true。现在,在到达“this.initialInt=val”的最后一条语句之前,另一个线程跳入,检查if条件并立即返回,从而打印未更改的initialInt值1。除此之外,我相信在没有任何volatile/synchronization的情况下,无法确定t2是否会看到在t1中对initialInt执行赋值,因此它也可能打印“1”作为默认值

对于b),我认为可以使该标志不稳定。我已经了解到,当t1写入一个易失性变量时,设置flag=true,那么t2在if语句中读取该易失性变量时,将看到在易失性写入之前执行的任何写入操作,因此initialInt=val。因此,t2将已经看到其initialInt值更改为0,并且必须始终打印0。 然而,只有当volatile的使用成功地阻止了我在a)中描述的任何重新排序时,这才有效。我读过关于volatile完成这些事情的文章,但我不确定在没有任何进一步的同步块或任何此类锁的情况下,这是否总是有效的。据我所知,在易失性存储区(因此this.flag=true)之前发生的任何事情都不能被重新排序以显示在它之外。在这种情况下,initialInt=val不能向下移动,我应该是正确的,对吗?还是没有?:)


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

此示例将始终打印00,因为您在打印之前会更改val(0)

要模拟可能不打印00的情况,需要移动
initialInt=1到线程的上下文,如下所示:

class MyThread extends Thread {
        public void run(){
            initialInt = 1;
            changeVal(0);
            System.out.print(initialInt);
        }
    }
现在您可能有了一个竞争条件,即在thread1中将initialInt设置回1,然后再在thread2中打印

另一种可能导致竞争条件但更难理解的方法是切换设置标志和设置值的顺序

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

没有显式同步,因此所有类型的交错都是可能的,并且一个线程所做的更改不一定对另一个线程可见,因此,对
标志的更改可能在对
初始值的更改之前可见,从而导致10或01输出,以及00输出。11是不可能的,因为对变量执行的操作对执行它们的线程是可见的,
changeVal(0)
的效果对至少一个线程总是可见的


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

感谢您的回答。那么,我是否正确地假设volatile确实排除了在this.initialInt=val(我们称之为赋值x)之间重新排序的可能性;那么这个.flag=true(y);?在没有volatile的情况下(除了您提到的其他交错),这个切换是可能的,对吗?所以我想澄清一下:如果我在x之前添加了一个额外的赋值z,即使带有volatile标志,编译器仍然可以任意切换z和x,但是,它不能将z或x移到y之外,这是真的吗?这是关于volatile的好信息,应该解释事情的行为:谢谢。但是这个开关是否只会在显式编码时出现,或者可能是编译器在缺少诸如volatile之类的同步功能的情况下对表达式重新排序的结果。然后,问题出现了,使用volatile flag变量是否会完全排除(^)发生这种重新排序的可能性。如果要以可扩展的方式完全同步线程,则需要使用高级构造,如临界区、互斥锁等。在这种简单的情况下,当您只有一个公共变量时,volatile可能会起作用。
void changeVal(int val) {
        if(this.flag){
            return;
        }
        this.flag = true;
        this.initialInt = val;
 }