[英]Deep understanding of volatile in Java
Does Java allows output 1, 0
? Java 允许输出
1, 0
吗? I've tested it very intensively and I cannot get that output.我已经对其进行了非常深入的测试,但无法获得该输出。 I get only
1, 1
or 0, 0
or 0, 1
.我只得到
1, 1
或0, 0
或0, 1
。
public class Main {
private int x;
private volatile int g;
// Executed by thread #1
public void actor1(){
x = 1;
g = 1;
}
// Executed by thread #2
public void actor2(){
put_on_screen_without_sync(g);
put_on_screen_without_sync(x);
}
}
Why?为什么?
On my eye it is possible to get 1, 0
.在我看来,有可能得到
1, 0
。 My reasoning.我的推理。
g
is volatile so it causes that memory order will be ensured. g
是易失性的,因此会确保内存顺序。 So, it looks like:所以,它看起来像:
actor1:
(1) store(x, 1)
(2) store(g, 1)
(3) memory_barrier // on x86
and, I see the following situation: reorder store(g, 1)
before store(x,1)
(memory_barrier is after (2)).并且,我看到以下情况:在
store(x,1)
store(g, 1)
重新排序(memory_barrier在(2) 之后)。 Now, run thread #2.现在,运行线程#2。 So,
g = 1, x = 0
.所以,
g = 1, x = 0
。 Now, we have expected output.现在,我们有了预期的输出。 What is incorrect in my reasoning?
我的推理有什么不正确的地方?
Any actions before a volatile write happen before (HB) any subsequent volatile read of the same variable.易失性写入之前的任何操作都发生在 (HB) 同一变量的任何后续易失性读取之前。 In your case, the write to
x
happens before the write to g
(due to program order).在您的情况下,写入
x
发生在写入g
之前(由于程序顺序)。
So there are only three possibilities:所以只有三种可能:
x=1
is executed (not g=1
) and the output could be either 0,1 or 0,0 (no volatile write so no guarantee)x=1
(不是g=1
)并且输出可以是 0,1 或 0,0 (没有易失性写入所以不能保证) No, this isn't possible.不,这是不可能的。 According to the JMM, anything that was visible to thread 1 when it writes to a volatile field becomes visible to thread 2 when it reads that field.
根据 JMM,线程 1 在写入可变字段时可见的任何内容在线程 2 读取该字段时都变得可见。
There is another example similar to yours provided here : 此处提供了另一个类似于您的示例:
class VolatileExample {
int x = 0;
volatile boolean v = false;
public void writer() {
x = 42;
v = true;
}
public void reader() {
if (v == true) {
//uses x - guaranteed to see 42.
}
}
}
You will never see 1, 0
, but properly explaining this is not going to be easy, spec wise.您永远不会看到
1, 0
,但正确地解释这并不容易,具体而言。 First let's get some obvious things out of the door.首先让我们把一些明显的东西拿出来。 The specification says :
规范说:
If x and y are actions of the same thread and x comes before y in program order , then hb(x, y).
如果 x 和 y 是同一个线程的动作,并且 x 在程序顺序中出现在 y 之前,则 hb(x, y)。
This means that on the writing thread side, hb(x, g)
and on the reading side hb(g, x)
.这意味着在写入线程端
hb(x, g)
和读取端hb(g, x)
。 But this is only so, if you would have to reason about each thread individually , as the chapter about Program order
says: :但仅此而已,如果你必须单独推理每个线程,正如关于
Program order
的章节所说:
Among all the inter-thread actions performed by each thread t...
在每个线程执行的所有线程间操作中...
So if you imagine running each thread at a time, then happens-before
would be correct for each of them, individually.因此,如果您想象一次运行每个线程,那么
happens-before
对每个线程都是正确的。 But you don't.但你没有。 Your actors (I am sure you use
jcstress
there) run concurrently.您的演员(我确定您在那里使用
jcstress
)同时运行。 So relying on "program order" for reasoning is not enough (neither it is correct).所以依靠“程序顺序”来推理是不够的(也不正确)。
You need to somehow synchronize these two actions now - the reading and the writing .您现在需要以某种方式同步这两个操作 -阅读和写作。 And here is how the specification says it can be done :
以下是规范所说的如何完成:
A write to a volatile variable synchronizes-with all subsequent reads of v by any thread (where "subsequent" is defined according to the synchronization order).
对 volatile 变量的写入与任何线程对 v 的所有后续读取同步(其中“后续”是根据同步顺序定义的)。
If an action x synchronizes-with a following action y, then we also have hb(x, y).
如果动作 x与后续动作 y 同步,那么我们也有 hb(x, y)。
If you put all of these together now:如果你现在把所有这些放在一起:
(hb) (hb) (hb)
write(x) ------> write(g) -------> read(g) -------> read(x)
This is also called to "transitively" close program order
and synchronizes-with order
.这也被称为“传递地”关闭
program order
并synchronizes-with order
。 Since there is hb
on every step, seeing 1, 0
(a racy read), is impossible according to the spec.由于每一步都有
hb
,因此根据规范不可能看到1, 0
(活泼的读取)。
No, and in fact this property of volatile
is used in classes like ConcurrentHashMap
to implement a lock-free happy path, roughly like this:不,事实上
volatile
的这个属性在ConcurrentHashMap
这样的类中用来实现无锁快乐路径,大致是这样的:
volatile int locked = 0;
...
void mutate() {
if (Unsafe.compareAndSwapInt(locked,0,1)) {
/*this isn't exactly how you call this method, but the point stands:
if we read 0, we atomically replace it with 1 and continue on the happy
path */
//we are happy
//so we mutate the structure and then
locked = 0;
} else {
//contended lock, we aren't happy
}
}
Since writes before a volatile write can't be reordered after the volatile write, and reads after volatile read can't be reordered before the volatile read, code like this indeed works as a "lockless locking".由于在易失性写入之前的写入不能在易失性写入之后重新排序,并且易失性读取之后的读取不能在易失性读取之前重新排序,所以这样的代码确实可以作为“无锁锁定”。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.