我对下面的代码段有疑问。结果可能有[0,1,0](这是用JCStress执行的测试)。那怎么会发生这种情况呢?我认为数据写入(data = 1)应该在写入Actor2中的guard2之前执行(guard2 = 1)。我对吗?我问,因为很多时候我已经阅读过指令,不会重新排序挥发物。而且根据这个:http://tutorials.jenkov.com/java-concurrency/volatile.html它写成如下:
JVM无法对volatile变量的读写指令进行重新排序(只要JVM检测到重新排序的程序行为没有变化,JVM可能会出于性能原因重新排序指令)。可以重新排序之前和之后的指令,但不能将易失性读或写与这些指令混合。无论何种指令,读取或写入后都会发生读取或写入。
所以如果我们不能重新排序
public class DoubleVolatileTest {
volatile int guard1 = 0;
int data = 0;
volatile int guard2 = 0;
@Actor
public void actor1() {
guard2 = 1;
data = 1;
guard1 = 1;
}
@Actor
public void actor2(III_Result r) {
r.r1 = guard1;
r.r2 = data;
r.r3 = guard2;
}
}
提前致谢!
答案 0 :(得分:1)
首先,这是
JVM无法对易失变量的读写指令进行重新排序...
表示不能重新排列易失性本身(易失性与易失性);但要注意的是
由于性能原因,JVM可能会重新排序指令,只要JVM从重新排序中未发现程序行为发生变化即可。
通常,JVM
关于重新排序(可能完成或无法完成)的推理是不正确的(我已阅读有关挥发物的指令未重新排序 ... )。重新排序/障碍/等不属于JLS
的一部分;相反,它仅在happens-before
规则的前提下起作用,这是您唯一需要关心的事情。
您的示例确实可以如以下注释中所述简化:
@Outcome(id = "0, 0", expect = Expect.ACCEPTABLE, desc = "don't care about this one")
@Outcome(id = "1, 0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "the one we care about")
@Outcome(id = "1, 1", expect = Expect.ACCEPTABLE, desc = "don't care about this one")
@Outcome(id = "0, 1", expect = Expect.ACCEPTABLE, desc = "don't care about this one")
@JCStressTest
@State
public class VolatileTest {
private volatile int guard = 0;
private int x = 0;
@Actor
void writeActor() {
guard = 1; // volatile store
// your reasoning is that these two operations should be re-ordered
// unfortunately, this is not correct.
x = 1; // plain store
}
@Actor
void readActor(II_Result r) {
r.r1 = x; // plain store
r.r2 = guard; // plain store
}
}
运行此命令实际上会导致1, 0
,这意味着x = 1
确实已与guard = 1
重新排序;实际上,实际上还有更多其他事情可能发生(但为简单起见,我们称它们为重排序,尽管这不是观察[1, 0]
的唯一原因)。
用JLS术语:您尚未在这些操作之间建立任何发生(例如典型的易失性存储/易失性负载),因此这些操作可以自由浮动周围。那几乎是答案的终点。更广泛的解释是volatile
(因为您使用了它)被这样说:
对易失性字段的写操作发生在每次对该字段的后续读取之前。
您没有对volatile guard
进行任何读取,因此无法保证。另一种解释方式是this excellent article甚至是this one。但是,即使您确实阅读了guard
,由于代码的设置方式,仍然无法保证重新排序。
volatile
仅在有成对用法时才正确工作,即Thread1
对volatile
字段执行写操作-Thread2
观察写操作。在这种情况下,按Thread2
可以看到按程序顺序在之前完成的所有操作(很明显,在看到该写入值之后)。或在代码中:
public class VolatileTest {
private volatile int guard = 0;
private int x = 0;
@Actor
void writeActor() {
// store comes "before" the store to volatile
// as opposed to the previous example
x = 1; // plain store
guard = 1; // volatile store
}
@Actor
void readActor(II_Result r) {
r.r1 = guard; // plain store
r.r2 = x; // plain store
}
}
现在JLS
向您保证,如果您看到guard
是1
,那么您还将观察到x
是1
({{这次无法在x = 1
以下重新排序1}}。因此,guard = 1
现在是非法的,因此从输出中看不到。