考虑两个线程:
A==B==0(初始)
| 线程1|线程2|
| - -----|- -----|
| B=42;| if(A==1)|
| A=1;|...print(B)|
据我所知,如果(至少)A
是volatile
,我们将只能在print
处读取B==42
。虽然如果只将B
标记为volatile
,我们可以读取B==42
,但也可以读取B==0
。
我想更仔细地看看只有B
是volatile
的情况,并理解为什么我们可以根据这些文档所说的来读取B==0
。为此,我开始添加所有程序顺序边缘并与文档中所述同步:
从B=42
到A=1
的两条边是简单程序顺序(PO)边,其余边与(SW)边同步。根据文档,当“* 对每个变量的默认值[...]的写入与每个线程中的第一个操作同步。”(这些是图片中的前4个边缘)和“ 对易失性变量v [...]的写入与v* 的所有后续读取同步”(从B=42
到print(B)
的边缘)时,我们有一个SW边缘。
现在,我们可以看看在边存在(HB)之前发生了什么,根据文档,这些边中的每一个也是HB排序。此外,对于所有hb(x,y)和hb(y,z),我们有hb(x,z)(这些边丢失,但我们仍然使用它们)。
最后,我们从文档中获得了我们可以在print(B)
上读取的内容:“我们说,如果在happens-before偏序[...]中,变量v的读r被允许观察到写w到v:
- r不在w之前排序(即,hb(r,w)不是这样),并且
- 不存在[...]对v的写入w'使得hb(w,w')和hb(w ',r)“
让我们看看是否可以在读r(print(B)
)时观察到写w(B=0
)。我们确实没有hb(r,w)。然而,我们确实具有插入hb(wow’)和hb(w’,r)的写入w’(B=42)。
这让我想知道我们是否能在打印时观察到B==0
,如果是的话,我对文档的推理或理解错在哪里?我想要一个明确的答案是指文件。
(我已经看过这个post,但是我希望得到一个更接近JMM文档的解释,我的问题也来自这个特定的代码)
3条答案
按热度按时间lrpiutwd1#
为了确保我理解正确:
这让我想知道我们是否可以在打印时观察到B==0,如果是的话,我的推理或对文档的理解错在哪里?
我们对执行感兴趣,其中:
A == 1
中A
读取器读取1
System.out.print(B)
中B
读取器读取0
就JMM操作而言,执行如下(为简洁起见,仅显示了
A
和B
上的操作):下面是执行中动作之间的 program-order 和 synchronizes-with(图中的
[po]
和[sw]
)关系:注意事项:
[sw]
边缘:将默认值(零、假或空)写入每个变量 * 与 * 每个线程中的第一个操作同步。
volatile-read(B):0
* 与 *volatile-write(B=0)
同步,因为以下规则:对volatile变量 v(§8.3.1.4)的写操作 * 与 * 任何线程对 v 的所有后续读取同步(其中“后续”是根据同步顺序定义的)。
顺便说一句,在我们的例子中,同步顺序是:
volatile-write(B=0)
->volatile-read(B):0
->volatile-write(B=42)
。根据同步顺序的定义,它是所有同步动作之间的全局顺序,与程序顺序一致。
读取
volatile-read(B):0
返回0
,因为以下规则:执行遵循同步顺序一致性。
对于 A 中的所有易失性读取 r,不是 so(r,W(r)) 或 A 中存在写入 w 使得 w.v = r.v 和 so(W(r),w) 和 so(w,r) 的情况。
volatile-write(B=42)
和volatile-read(B):0
之间没有[sw]
边。这是因为根据下面的规则,volatile写操作只与变量的volatile读操作同步,这些读操作在同步顺序中较晚:对volatile变量
v
(§8.3.1.4)的写入 * 与 * 任何线程对v
的所有后续读取同步(其中“后续”是根据同步顺序定义的)。write(A=1)
和read(A):1
之间没有[sw]
边缘,因为它们是普通的写入和读取,但是[sw]
仅用于同步操作下面是动作之间的 happens-before 关系(由
[po]
和[sw]
构建):根据JLS,发生在一致性之前:
我们说一个变量
v
的读r
被允许观察一个w
到v
的写,如果,在执行跟踪的 happens-before 部分顺序中:r
不排序在w
之前(即,不是hb(r, w)
的情况),并且不存在插入写入
w'
到v
(即,no writew'
tov
so thathb(w, w')
andhb(w', r)
).非正式地,如果没有happens-before命令来阻止读取,则允许读取
r
查看写入w
的结果。一组动作
A
是happens-before一致的,如果对于A
中的所有读取r
,其中W(r)
是r
看到的写入动作,情况不是hb(r, W(r))
或者在A
中存在写w
,使得w.v = r.v
和hb(W(r), w)
和hb(w, r)
。在happens-before一致的操作集合中,每个读都看到happens-before排序允许它看到的写。
对于
read(A):1
,Happens-before一致性没有被破坏,因为正如您在上面的图表中所看到的,write(A=1)
和read(A):1
之间没有happens-before关系。volatile-read(B):0
也很好(上面解释过)。事实上,我在JMM中没有看到任何在这次执行中违反的东西--所以根据JMM,IMO的执行是法律的的。
0md85ypi2#
我相信你的误解是写
B=42
。由于线程2直到print(B)
语句才读取B
,因此在读取A
之前,B
没有happens before关系。因此,B=42
的写入不会影响线程2对A
的读取。因此线程2可以在
B=42
写入之前观察到A=1
的写入。s3fp2yjn3#
从
B=42
到A=1
的两条边是简单程序顺序(PO)边使
B
易失性保证了线程1在B=42
赋值之前 * 按程序顺序 * 所做的任何事情,在线程2从B
读取42时,必须对线程2可见。但是,在您的示例中,线程1在分配B=42
之前什么都不做。所以,* nothing * 就是volatile read所保证的。有件事可能会发生。编译器可以重新排序线程1中的两个赋值,或者硬件可以乱序存储值。如果它不改变线程1本身发生的任何事情的结果,那么它就不违反“程序顺序”。所以,事情可能会这样发展:
我不知道为什么这些作业会被重新排序,但我很确定规则允许它们被重新排序。