Java 中多次写入 volatile 变量的影响
effects of several writes on a volatile variable in Java
我在这个网站上发现了一些有趣的问题(例如,this one) about the visibility effects of volatile variables in Java originated from this paragraph taken from the book Java concurrency in Practice:
The visibility effects of volatile variables extend beyond the value of the volatile variable itself. When thread A writes to a volatile variable and subsequently thread B reads that same variable, the values of all variables that were visible to A prior to writing to the volatile variable become visible to B after reading the volatile variable. So from a memory visibility perspective, writing a volatile variable is like exiting a synchronized block and reading a volatile variable is like entering a synchronized block.
然而,即使在阅读了本网站相关问题的答案后,我仍然不完全清楚一个场景,具体来说:
线程 A0 在 线程 A[ 之前写入相同的易失性变量 会产生什么影响=47=]?
换句话说:A0 写入 volatile 变量,此值稍后被 A 覆盖( 不读取变量) 然后由 B 读取。 (所以我们有两个来自不同线程的写操作(A0 和 A)和一个来自第三个线程的读操作(B )).
我可以安全地假设 A 和 B 都能保证看到 一切在 A0 写入 volatile 变量之前对 A0 可见?
更新:
这是一个关于 Java 内存模型的概念性问题。
我知道我无法预测在线程 A0 和 A 中写入 volatile 变量的顺序以及在线程 [=25= 中读取的顺序]B。
不过,为了方便讨论,还是说
A0 在 A 开始之前启动了很多时间,并且在另一个重要的时间 B 开始之后,并让我们做一个简化的假设,即这足以保证写入和读取按描述的顺序发生(我知道顺序不能仅通过时间来保证,这只是为了避免与原始问题不同而进行的简化) .
Can I safely assume that both A and B are guaranteed to see everything that was visible to A0 before A0 wrote to the volatile variable?
写入 volatile 不会为线程提供任何关于读取的先行发生保证。
当 B
读取时,它将看到 A0 可以看到的任何内容,前提是它看到它所做的更新。 B
也可以看到任何 A
可以看到的东西,只要它看到 A
写的值。否则,如果它读取的值不是来自那个线程,即它太早,B
可能会看到比任一线程更早的状态。
如果 A0
尝试先写入,它可能会在 A
之后完成,例如假设 A
与数据的干净副本位于同一套接字上。它可以在 A0
之前访问它,因为后者需要更长的时间来获取缓存行。最后完成的那个决定了数据的最终状态。
我觉得场景很简单
A0 - 写入可变变量 V 值 WA0
A - 写入可变变量 V 值 WA
现在
Can I safely assume that both A and B are guaranteed to see everything
that was visible to A0 before A0 wrote to the volatile variable?
如果线程 A 仅写入 V ,那么它可能会或可能不会看到写入 V 之前 A0 可见的所有内容。
只有线程A读取了变量V,读取到的值结果是WA0,才能保证线程A看到一切可见在写入 V.
之前到 A0
线程 B 也是如此,这取决于 B 在读取 V 的值后看到的是哪个值。
如果 B 将 V 的值读取为 WA,那么它将看到在线程 A 中写入 V 之前发生的所有事情。
如果 B 将 V 的值读取为 WA0,那么它将看到在线程 A0 中写入 V 之前发生的所有事情。
另外请记住
Thread A0
a = 1; // non volatile write
V = WA0; // volatile write
Thread A;
a=3
V = WA; // volatile write
Thread B;
while(V == 'WA') {
assert(a,3); // This may fail
}
您需要了解线程 b 中的代码未正确同步
volatile 提供与可见性和重新排序相关的保证,但不保证原子性。
所以即使线程 B 读取 V 的值是 'WA' 并且它保证在写入 V 之前看到线程 A 中发生的所有事情,这并不一定意味着它会看到的值a 为 3,因为很可能在读取 V 的值后,线程 A0 将 a 作为 1 写入线程 B,从而使您的断言失败。 Happens before 向您保证在写入 v 之前必须发生的所有事情都已经发生,但这并不意味着您看不到未来的价值。
您可以通过执行类似的操作轻松重现该场景
Thread B;
while(V == 'WA') {
Thread.sleep(1000);
assert(a,3); // This may fail
}
因此,在这些情况下,Single Writer 更可取,否则您的程序不应在线程 B 中包含上述代码,因为这样您的程序中就会出现数据竞争。
编辑:
示例修改:
Thread 1 (T1)
a = 1; // normal write
b = 1; // normal write
v = 1; // volatile write
Thread 2 (T2)
a = 2; // normal write
c = 2; // normal write
v = 2; // volatile write
Thread 3 (T3)
while(true) {
if(v == 2) {
assert (c == 2); // will pass
assert (b == 1); // may fail if T1 hasn't run till
assert (a == 2); // may fail if T1 has run and set the value to 1
break;
}
if(v == 1) {
assert (b == 1); // will pass
assert (c == 2); // may fail if T2 hasn't run till
assert (a == 1); // may fail if T2 has now run setting a == 2
break;
}
}
" for the sake of facilitating the discussion, lets say that A0
starts lots of time before A does it, and after another significant
amount of time B starts." as OP states
Now if i restate the above statement as (v == 1 happens before
(hb) v == 2 ) .
i.e i assume i have a guarantee that v==1 hb (v==2)
然后线程 3 的行为将更改为
线程 3 (T3)
while(true) {
if(v == 2) {
assert (c == 2); // will pass
assert (b == 1); // will pass
assert (a == 2); // will pass
break;
}
if(v == 1) {
assert (b == 1); // will pass
assert (c == 2); // may fail if T2 hasn't run till
assert (a == 1); // may fail if T2 has now run setting a == 2
break;
}
}
上述行为的问题是你如何保证
v==1 hb (v==2)
我想如果你能理解如何建立上述保证,那么你自己就有答案了。
一种方法是建立@ishrat 在线程 A 中所做的方式。
jmm.
中还有其他方法
但是光靠时间是无法保证的,你需要依赖你所使用的语言规范和平台的底层保证。
也请阅读这篇优秀的文章article about jmm
A write to a volatile variable v (§8.3.1.4) synchronizes-with all subsequent reads of v by any thread (where "subsequent" is defined according to the synchronization order).
JLS 17.4.4
因此,来自 A0 的写入 与来自 B 的读取 同步。所有来自 A0 的较早写入发生在 以下来自 B 的读取之前.如果没有其他人写这些变量,那么是的,B一定看到了。
我找不到A与A0同步。因此,无法保证 A 中的可见性。
根据更新的问题,我们有以下执行,其中 A0、A1 和 B 是线程,t0、t1、t2 是语句,writeV、readV 是 writes/reads 到 volatile V:
A0 A1 B
t0 <-- writeV
^ t1 <-- writeV
| ^
| \------readV <-- t2
\-----------------------/
箭头表示发生在之前。我们有 t0 和 t1 happensbefore t2。 t2 将看到来自 t0 和 t1 的更新。
因此,是的:B 保证可以看到 A0 在 t0 中写入的所有内容。并且会读取A.
写入的volatile值
但是不是 t0 happensbefore t,反之亦然。
因此,不:不能保证 A 看到 A0 写的所有内容。
然而,这只是概念上的,在现实生活中B根本不知道A0是否执行了。
Can I safely assume that both A and B are guaranteed to see everything that was visible to A0 before A0 wrote to the volatile variable?
否。对于易失性存储,当涉及到谓词时,就会构建先行发生的边缘。例如,为了让线程 A
看到 A0
的写入完成的所有写入,它需要在易失性写入上断言。
int a,b;
volatile c;
A0:
a = 10;
b = 5;
c = 20;
A:
if(c == 20){
// here all writes by A0 are guaranteed to be visible (ie: a and b).
c = 50;
}
如果我们引入第三个线程 B
需要进行类似的检查:
int a,b;
volatile c;
A0:
a = 10;
b = 5;
c = 20;
A:
if(c == 20){
c = 50;
}
B:
if(c == 50){
// B will see all writes done by A and also A0.
}
因此,您正在寻找的保证与这些由写入和后续读取定义的先行边相关联。
否则,线程 A 和 B 可以读取 a
、b
和 c
作为它们的默认值 0 或写入的值。
However, for the sake of facilitating the discussion, lets say that A0 starts lots of time before A does it, and after another significant amount of time B starts.
在实践中,您可能不会 运行 遇到内存问题,但您永远不应该提前带着这种想法编写软件。
此外,我怀疑你是这个意思,但如果你真的是想 start - 一个线程的写入发生在之前另一个线程的开始(开始发生在同一个线程中)。
编辑您的评论。
如果我们删除来自 A
的读取但保留来自 B
的读取,则每个线程的可能值为
int a,b;
volatile c;
A0:
a = 10;
b = 5;
c = 20;
A:
int l = c; // c could be either 20 or 0
int j = a; // a could be either 10 or 0
int k = b; // b could be either 5 or 0
c = 50;
B:
if(c == 50){
int l = c; // c can be 20 or 50. c cannot be 0
int j = a; // a could be either 10 or 0
int k = b; // b could be either 5 or 0
}
如您所见,线程 A0
的写入与 A
或 B
的 reads/writes 之间没有关系,除非我们有读取前提条件.
我在这个网站上发现了一些有趣的问题(例如,this one) about the visibility effects of volatile variables in Java originated from this paragraph taken from the book Java concurrency in Practice:
The visibility effects of volatile variables extend beyond the value of the volatile variable itself. When thread A writes to a volatile variable and subsequently thread B reads that same variable, the values of all variables that were visible to A prior to writing to the volatile variable become visible to B after reading the volatile variable. So from a memory visibility perspective, writing a volatile variable is like exiting a synchronized block and reading a volatile variable is like entering a synchronized block.
然而,即使在阅读了本网站相关问题的答案后,我仍然不完全清楚一个场景,具体来说:
线程 A0 在 线程 A[ 之前写入相同的易失性变量 会产生什么影响=47=]? 换句话说:A0 写入 volatile 变量,此值稍后被 A 覆盖( 不读取变量) 然后由 B 读取。 (所以我们有两个来自不同线程的写操作(A0 和 A)和一个来自第三个线程的读操作(B )).
我可以安全地假设 A 和 B 都能保证看到 一切在 A0 写入 volatile 变量之前对 A0 可见?
更新:
这是一个关于 Java 内存模型的概念性问题。 我知道我无法预测在线程 A0 和 A 中写入 volatile 变量的顺序以及在线程 [=25= 中读取的顺序]B。 不过,为了方便讨论,还是说 A0 在 A 开始之前启动了很多时间,并且在另一个重要的时间 B 开始之后,并让我们做一个简化的假设,即这足以保证写入和读取按描述的顺序发生(我知道顺序不能仅通过时间来保证,这只是为了避免与原始问题不同而进行的简化) .
Can I safely assume that both A and B are guaranteed to see everything that was visible to A0 before A0 wrote to the volatile variable?
写入 volatile 不会为线程提供任何关于读取的先行发生保证。
当 B
读取时,它将看到 A0 可以看到的任何内容,前提是它看到它所做的更新。 B
也可以看到任何 A
可以看到的东西,只要它看到 A
写的值。否则,如果它读取的值不是来自那个线程,即它太早,B
可能会看到比任一线程更早的状态。
如果 A0
尝试先写入,它可能会在 A
之后完成,例如假设 A
与数据的干净副本位于同一套接字上。它可以在 A0
之前访问它,因为后者需要更长的时间来获取缓存行。最后完成的那个决定了数据的最终状态。
我觉得场景很简单
A0 - 写入可变变量 V 值 WA0
A - 写入可变变量 V 值 WA
现在
Can I safely assume that both A and B are guaranteed to see everything that was visible to A0 before A0 wrote to the volatile variable?
如果线程 A 仅写入 V ,那么它可能会或可能不会看到写入 V 之前 A0 可见的所有内容。
只有线程A读取了变量V,读取到的值结果是WA0,才能保证线程A看到一切可见在写入 V.
之前到 A0线程 B 也是如此,这取决于 B 在读取 V 的值后看到的是哪个值。
如果 B 将 V 的值读取为 WA,那么它将看到在线程 A 中写入 V 之前发生的所有事情。
如果 B 将 V 的值读取为 WA0,那么它将看到在线程 A0 中写入 V 之前发生的所有事情。
另外请记住
Thread A0
a = 1; // non volatile write
V = WA0; // volatile write
Thread A;
a=3
V = WA; // volatile write
Thread B;
while(V == 'WA') {
assert(a,3); // This may fail
}
您需要了解线程 b 中的代码未正确同步 volatile 提供与可见性和重新排序相关的保证,但不保证原子性。
所以即使线程 B 读取 V 的值是 'WA' 并且它保证在写入 V 之前看到线程 A 中发生的所有事情,这并不一定意味着它会看到的值a 为 3,因为很可能在读取 V 的值后,线程 A0 将 a 作为 1 写入线程 B,从而使您的断言失败。 Happens before 向您保证在写入 v 之前必须发生的所有事情都已经发生,但这并不意味着您看不到未来的价值。 您可以通过执行类似的操作轻松重现该场景
Thread B;
while(V == 'WA') {
Thread.sleep(1000);
assert(a,3); // This may fail
}
因此,在这些情况下,Single Writer 更可取,否则您的程序不应在线程 B 中包含上述代码,因为这样您的程序中就会出现数据竞争。
编辑:
示例修改:
Thread 1 (T1)
a = 1; // normal write
b = 1; // normal write
v = 1; // volatile write
Thread 2 (T2)
a = 2; // normal write
c = 2; // normal write
v = 2; // volatile write
Thread 3 (T3)
while(true) {
if(v == 2) {
assert (c == 2); // will pass
assert (b == 1); // may fail if T1 hasn't run till
assert (a == 2); // may fail if T1 has run and set the value to 1
break;
}
if(v == 1) {
assert (b == 1); // will pass
assert (c == 2); // may fail if T2 hasn't run till
assert (a == 1); // may fail if T2 has now run setting a == 2
break;
}
}
" for the sake of facilitating the discussion, lets say that A0 starts lots of time before A does it, and after another significant amount of time B starts." as OP states
Now if i restate the above statement as (v == 1 happens before (hb) v == 2 ) .
i.e i assume i have a guarantee that v==1 hb (v==2)
然后线程 3 的行为将更改为
线程 3 (T3)
while(true) {
if(v == 2) {
assert (c == 2); // will pass
assert (b == 1); // will pass
assert (a == 2); // will pass
break;
}
if(v == 1) {
assert (b == 1); // will pass
assert (c == 2); // may fail if T2 hasn't run till
assert (a == 1); // may fail if T2 has now run setting a == 2
break;
}
}
上述行为的问题是你如何保证
v==1 hb (v==2)
我想如果你能理解如何建立上述保证,那么你自己就有答案了。
一种方法是建立@ishrat 在线程 A 中所做的方式。 jmm.
中还有其他方法但是光靠时间是无法保证的,你需要依赖你所使用的语言规范和平台的底层保证。
也请阅读这篇优秀的文章article about jmm
A write to a volatile variable v (§8.3.1.4) synchronizes-with all subsequent reads of v by any thread (where "subsequent" is defined according to the synchronization order). JLS 17.4.4
因此,来自 A0 的写入 与来自 B 的读取 同步。所有来自 A0 的较早写入发生在 以下来自 B 的读取之前.如果没有其他人写这些变量,那么是的,B一定看到了。
我找不到A与A0同步。因此,无法保证 A 中的可见性。
根据更新的问题,我们有以下执行,其中 A0、A1 和 B 是线程,t0、t1、t2 是语句,writeV、readV 是 writes/reads 到 volatile V:
A0 A1 B
t0 <-- writeV
^ t1 <-- writeV
| ^
| \------readV <-- t2
\-----------------------/
箭头表示发生在之前。我们有 t0 和 t1 happensbefore t2。 t2 将看到来自 t0 和 t1 的更新。
因此,是的:B 保证可以看到 A0 在 t0 中写入的所有内容。并且会读取A.
写入的volatile值但是不是 t0 happensbefore t,反之亦然。 因此,不:不能保证 A 看到 A0 写的所有内容。
然而,这只是概念上的,在现实生活中B根本不知道A0是否执行了。
Can I safely assume that both A and B are guaranteed to see everything that was visible to A0 before A0 wrote to the volatile variable?
否。对于易失性存储,当涉及到谓词时,就会构建先行发生的边缘。例如,为了让线程 A
看到 A0
的写入完成的所有写入,它需要在易失性写入上断言。
int a,b;
volatile c;
A0:
a = 10;
b = 5;
c = 20;
A:
if(c == 20){
// here all writes by A0 are guaranteed to be visible (ie: a and b).
c = 50;
}
如果我们引入第三个线程 B
需要进行类似的检查:
int a,b;
volatile c;
A0:
a = 10;
b = 5;
c = 20;
A:
if(c == 20){
c = 50;
}
B:
if(c == 50){
// B will see all writes done by A and also A0.
}
因此,您正在寻找的保证与这些由写入和后续读取定义的先行边相关联。
否则,线程 A 和 B 可以读取 a
、b
和 c
作为它们的默认值 0 或写入的值。
However, for the sake of facilitating the discussion, lets say that A0 starts lots of time before A does it, and after another significant amount of time B starts.
在实践中,您可能不会 运行 遇到内存问题,但您永远不应该提前带着这种想法编写软件。
此外,我怀疑你是这个意思,但如果你真的是想 start - 一个线程的写入发生在之前另一个线程的开始(开始发生在同一个线程中)。
编辑您的评论。
如果我们删除来自 A
的读取但保留来自 B
的读取,则每个线程的可能值为
int a,b;
volatile c;
A0:
a = 10;
b = 5;
c = 20;
A:
int l = c; // c could be either 20 or 0
int j = a; // a could be either 10 or 0
int k = b; // b could be either 5 or 0
c = 50;
B:
if(c == 50){
int l = c; // c can be 20 or 50. c cannot be 0
int j = a; // a could be either 10 or 0
int k = b; // b could be either 5 or 0
}
如您所见,线程 A0
的写入与 A
或 B
的 reads/writes 之间没有关系,除非我们有读取前提条件.