首先我要说的是,我知道这是一个相当常见的主题,但在搜索它时我找不到另一个问题来澄清以下情况。如果这可能是重复的,我很抱歉,但你可以:
我是并发新手,为了回答问题,我得到了以下代码:
- a) 为什么除了“00”之外还有其他输出?
- b) 如何修改代码以便始终打印“00”。
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 完成此类事情的信息,但我不确定在没有任何进一步的同步块(synchronized block)或任何此类锁的情况下这是否总是有效。来自 this answer我发现在 volatile 存储(因此 this.flag = true)之前发生的任何事情都不能被重新排序以使其出现在其之外。在这种情况下,initialInt = val 无法向下移动,我应该是正确的,对吧?或不 ? :)
非常感谢您的帮助。我期待您的回复。
最佳答案
此示例将始终打印 00 ,因为您在打印之前执行了 changeVal(0)
。
为了模拟可能不会打印 00 的情况,您需要将 initialInt = 1;
移动到线程的上下文,如下所示:
class MyThread extends Thread {
public void run(){
initialInt = 1;
changeVal(0);
System.out.print(initialInt);
}
}
现在您可能遇到竞争条件,在线程 1 中将initialInt 设置回 1,然后再在线程 2 中打印
另一种可能导致竞争条件但更难理解的替代方案是切换设置标志和设置值的顺序
void changeVal(int val) {
if(this.flag){
return;
}
this.flag = true;
this.initialInt = val;
}
关于java - volatile Java 重新排序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63162001/