易失性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”
非常感谢你的帮助。我期待您的回复。此示例将始终打印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;
}