Rol*_*and 18 java multithreading
假设有两个没有同步的线程,一个设置n = 1另一个执行method().
在下面的"读取"中总是指读取字段n.
public class MyClass
{
public int n = 0;
public void method() {
System.out.println(n); //read 1
System.out.println(n); //read 2
}
}
Run Code Online (Sandbox Code Playgroud)
以下输出是否可行?
1
0
Run Code Online (Sandbox Code Playgroud)
答案是肯定的,因为即使读取1发生在读取2之前,仍然可以在读取1之前重新排序读取2,因为它不会改变线程内执行的语义.
这个推理是否正确?
Ale*_*lev 30
Happens-before并不意味着两个任意操作的顺序.更精确地说,出现这种情况,之前所做的最重要的事情是占用的写入和读取在之前发生的一致性.值得注意的是,它告诉读取观察的写入内容:最后一次写入发生 - 在顺序之前,或者在发生之前(竞赛)中没有排序的任何其他写入.请注意,两次连续读取可能会看到从不同(racy)写入获得的不同值,而不会违反该要求.
例如JLS 17.4.5说:
应该注意的是,两个动作之间存在的先发生关系并不一定意味着它们必须在实现中以该顺序发生.如果重新排序产生的结果与合法执行一致,则不是非法的.
数据竞争令人毛骨悚然:racy读取可以在每次读取时返回令人惊讶的数据,Java内存模型捕获了这些数据.因此,更精确的答案是产生(1,0)的执行不违反Java内存模型约束(同步顺序一致性,同步顺序 - 程序顺序一致性,发生在一致性,因果关系要求之外),因此允许.
实现方式:在硬件上,两个负载都可以在不同的时间启动和/或到达内存子系统,而不管它们的"程序顺序",因为它们是独立的; 在编译器中,指令调度也可以忽略独立读取的程序顺序,以"反直觉"的顺序将负载暴露给硬件.
如果要在程序顺序中观察读取,则需要更强的属性.JMM将该属性赋予同步操作(在您的示例中,使变量volatile成为可能),这将以与程序顺序一致的总同步顺序绑定操作.在这种情况下,(1,0)将被禁止.
关于一个非常特殊的jcstress测试用例的插图(请参阅警告的完整来源):
private final Holder h1 = new Holder();
private final Holder h2 = h1;
private static class Holder {
int a;
int trap;
}
@Actor
public void actor1() {
h1.a = 1;
}
@Actor
public void actor2(IntResult2 r) {
Holder h1 = this.h1;
Holder h2 = this.h2;
h1.trap = 0;
h2.trap = 0;
r.r1 = h1.a;
r.r2 = h2.a;
}
Run Code Online (Sandbox Code Playgroud)
即使在没有重新排序负载的x86上,yield(1,0),oops:
[OK] o.o.j.t.volatiles.ReadAfterReadTest
(fork: #1, iteration #1, JVM args: [-server])
Observed state Occurrences Expectation Interpretation
[0, 0] 16,736,450 ACCEPTABLE Doing both reads early.
[1, 1] 108,816,262 ACCEPTABLE Doing both reads late.
[0, 1] 3,941 ACCEPTABLE Doing first read early, not surprising.
[1, 0] 84,477 ACCEPTABLE_INTERESTING First read seen racy value early, and the s...
Run Code Online (Sandbox Code Playgroud)
使Holder.avolatile变为(1,0)会消失.