问题描述
考虑这个例子.我们有:
int var = 0;
线程 A:
System.out.println(var);System.out.println(var);
线程 B:
var = 1;
线程并发运行.以下输出可能吗?
10
即在读取新值后读取原始值.var
不是可变的.我的直觉是这是不可能的.
您正在使用 System.out.println
在内部执行 synchronized(this) {...}
代码> 这会让事情变得更糟.但即便如此,您的阅读器线程仍然可以观察到 1, 0
,即:阅读.
我目前还不是这方面的专家,但在浏览了 Alexey Shipilev 的大量视频/示例/博客后,我想我至少了解了一些东西.
JLS 状态 :
如果 x 和 y 是同一个线程的动作,并且 x 在程序顺序中排在 y 之前,则 hb(x, y).
由于 var
的两次读取都在 program order
中,我们可以绘制:
(po)firstRead(var) ------>第二读(var)//po == 程序顺序
这句话也说这建立了一个happens-before
顺序,所以:
(hb)firstRead(var) ------>第二读(var)//hb == 发生在之前
但那是在同一个线程"中.如果我们想推理多线程,我们需要查看 同步顺序.我们需要它,因为关于 happens-before order
的同一段说:
如果一个动作 x 与后续动作 y 同步,那么我们也有 hb(x, y).
因此,如果我们在 program order
和 synchronizes-with order
之间构建这个动作链,我们可以对结果进行推理.让我们将其应用于您的代码:
(NO SW) (hb)写(变量)--------->firstRead(var) ------->第二读(var)//NO SW == 没有没有同步顺序";这里//hb == 发生在之前
这就是 发生在一致性之前
在 同一章节:
如果对于 A 中的所有读取 r,其中 W(r) 是 r 看到的写入操作,则一组动作 A 是发生前一致的,如果 hb(r, W(r))或者在 A 中存在写 w 使得 wv = rv 和 hb(W(r), w) 和 hb(w, r).
在一个happens-before一致的动作集合中,每次读看到一个写,它被happens-before排序允许看到
我承认我对第一句话的理解非常模糊,正如阿列克谢所说,这是对我帮助最大的地方:
读取要么看到 happens-before
中发生的最后一次写入,要么看到任何其他写入.
因为那里没有synchronizes-with order
,并且隐含地没有happens-before order
,所以允许读取线程通过竞争进行读取.从而得到1
,而不是0
.
一旦您引入正确的synchronizes-with order
,例如来自这里的一个
监视器 m 上的解锁操作与...的所有后续锁定操作同步
对易失性变量 v 的写入与任何线程对 v 的所有后续读取同步...
图表发生变化(假设您选择使 var
volatile
):
SW PO写(变量)--------->firstRead(var) ------->第二读(var)//SW == 存在与订单同步";这里//PO == 发生在之前
PO
(程序顺序)通过我在 JLS 的这个答案中引用的第一句话给出了 HB
(之前发生过).而 SW
给出 HB
因为:
如果一个动作 x 与随后的动作 y 同步,那么我们也有 hb(x, y).
因此:
HB HB写(变量)--------->firstRead(var) ------->第二读(var)
现在happens-before order
表示读取线程将读取写入最后一个HB"的值,或者意味着读取1
然后0
是不可能的.
我举了例子 jcstress 样本 并引入了一个小的变化(就像你的 System.out.println
所做的那样):
@JCStressTest@Outcome(id = 0, 0",expect = Expect.ACCEPTABLE,desc = 尽早读取.")@Outcome(id = "1, 1", expect = Expect.ACCEPTABLE, desc = "Doing two readings Late.")@Outcome(id = "0, 1", expect = Expect.ACCEPTABLE, desc = "提前阅读,不足为奇.")@Outcome(id = "1, 0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "第一次阅读很早就看到了活泼的价值,第二次没有.")@状态公共类 SO64983578 {私人最终持有人 h1 = 新持有人();私人最终持有人 h2 = h1;私有静态类持有人{一个;内部陷阱;}@演员公共无效演员1(){h1.a = 1;}@演员公共无效actor2(II_Result r){持有人 h1 = this.h1;持有人 h2 = this.h2;h1.trap = 0;h2.trap = 0;同步(这个){r.r1 = h1.a;}同步(这个){r.r2 = h2.a;}}}
注意 synchronized(this){....}
不是初始示例的一部分.即使使用同步,我仍然可以看到 1, 0
结果.这只是为了证明即使使用 synchronized
(内部来自 System.out.println
),您仍然可以获得 1
而不是 0
.
Consider this example. We're having:
int var = 0;
Thread A:
System.out.println(var);
System.out.println(var);
Thread B:
var = 1;
The threads run concurrently. Is the following output possible?
1
0
That is, the original value is read after the new value was read. The var
isn't volatile. My gut feeling is that it's not possible.
You are using System.out.println
that internally does a synchronized(this) {...}
that will make things a bit more worse. But even with that, your reader thread can still observe 1, 0
, i.e. : a racy read.
I am by far not an expert of this, but after going through lots of videos/examples/blogs from Alexey Shipilev, I think I understand at least something.
JLS states that :
Since both reads of var
are in program order
, we can draw:
(po)
firstRead(var) ------> secondRead(var)
// po == program order
That sentence also says that this builds a happens-before
order, so:
(hb)
firstRead(var) ------> secondRead(var)
// hb == happens before
But that is within "the same thread". If we want to reason about multiple threads, we need to look into synchronization order. We need that because the same paragraph about happens-before order
says:
So if we build this chain of actions between program order
and synchronizes-with order
, we can reason about the result. Let's apply that to your code:
(NO SW) (hb)
write(var) ---------> firstRead(var) -------> secondRead(var)
// NO SW == there is "no synchronizes-with order" here
// hb == happens-before
And this is where happens-before consistency
comes at play in the same chapter:
I admit that I very vaguely understand the first sentence and this is where Alexey has helped me the most, as he puts it:
Because there is no synchronizes-with order
there, and implicitly there is no happens-before order
, the reading thread is allowed to read via a race.and thus get 1
, than 0
.
As soon as you introduce a correct synchronizes-with order
, for example one from here
The graph changes (let's say you chose to make var
volatile
):
SW PO
write(var) ---------> firstRead(var) -------> secondRead(var)
// SW == there IS "synchronizes-with order" here
// PO == happens-before
PO
(program order) gives that HB
(happens before) via the first sentence I quoted in this answer from the JLS. And SW
gives HB
because:
As such:
HB HB
write(var) ---------> firstRead(var) -------> secondRead(var)
And now happens-before order
says that the reading thread will read the value that was "written in the last HB", or it means that reading 1
then 0
is impossible.
I took the example jcstress samples and introduced a small change (just like your System.out.println
does):
@JCStressTest
@Outcome(id = "0, 0", expect = Expect.ACCEPTABLE, desc = "Doing both reads early.")
@Outcome(id = "1, 1", expect = Expect.ACCEPTABLE, desc = "Doing both reads late.")
@Outcome(id = "0, 1", expect = Expect.ACCEPTABLE, desc = "Doing first read early, not surprising.")
@Outcome(id = "1, 0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "First read seen racy value early, and the second one did not.")
@State
public class SO64983578 {
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(II_Result r) {
Holder h1 = this.h1;
Holder h2 = this.h2;
h1.trap = 0;
h2.trap = 0;
synchronized (this) {
r.r1 = h1.a;
}
synchronized (this) {
r.r2 = h2.a;
}
}
}
Notice the synchronized(this){....}
that is not part of the initial example. Even with synchronization, I still can see that 1, 0
as a result. This is just to prove that even with synchronized
(that comes internally from System.out.println
), you can still get 1
than 0
.
这篇关于在读取较新的值后读取过时的值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!