【发布时间】:2014-08-19 13:57:09
【问题描述】:
在阅读了更多博客/文章等之后,我现在对内存屏障之前/之后的加载/存储行为感到非常困惑。
以下是 Doug Lea 在他的一篇关于 JMM 的澄清文章中引用的两句话,这两句话都非常直截了当:
- 线程 A 在写入 volatile 字段 f 时可见的任何内容在线程 B 读取 f 时变为可见。
- 请注意,两个线程访问同一个 volatile 变量以正确设置发生前的关系非常重要。并不是线程 A 在写入 volatile 字段 f 时对它可见的所有内容在它读取 volatile 字段 g 后对线程 B 都是可见的。
但是当我查看另一个关于内存屏障的 blog 时,我得到了这些:
- 存储屏障,x86 上的“sfence”指令,强制屏障之前的所有存储指令发生在屏障之前,并将存储缓冲区刷新到发出它的 CPU 的缓存中。
- 加载屏障,x86 上的“lfence”指令,强制屏障之后的所有加载指令发生在屏障之后,然后等待加载缓冲区为该 CPU 耗尽。
对我来说,Doug Lea 的澄清比另一个更严格:基本上,这意味着如果负载屏障和存储屏障在不同的监视器上,将无法保证数据的一致性。但后者意味着即使障碍在不同的监视器上,也能保证数据的一致性。我不确定我是否正确理解了这两个,也不确定其中哪个是正确的。
考虑以下代码:
public class MemoryBarrier {
volatile int i = 1, j = 2;
int x;
public void write() {
x = 14; //W01
i = 3; //W02
}
public void read1() {
if (i == 3) { //R11
if (x == 14) //R12
System.out.println("Foo");
else
System.out.println("Bar");
}
}
public void read2() {
if (j == 2) { //R21
if (x == 14) //R22
System.out.println("Foo");
else
System.out.println("Bar");
}
}
}
假设我们有 1 个写线程 TW1 首先调用 MemoryBarrier 的 write() 方法,然后我们有 2 个读线程 TR1 和 TR2 调用 MemoryBarrier 的 read1() 和 read2() 方法。考虑这个程序运行在不保存的 CPU 上排序(x86 DO 保留这种情况的排序,但情况并非如此),根据内存模型,W01/W02 之间会有一个 StoreStore 屏障(比如说 SB1),以及 R11/R12 和 R21/ 之间的 2 个 LoadLoad 屏障R22(比方说 RB1 和 RB2)。
- 由于 SB1 和 RB1 在同一个监视器 i 上,所以调用 read1 的线程 TR1 应该总是在 x 上看到 14,而且总是打印“Foo”。李>
- SB1 和 RB2 在不同的显示器上,如果 Doug Lea 正确,线程 TR2 将不能保证在 x 上看到 14,这意味着可能偶尔会打印“Bar”。但是如果内存屏障像blog 中描述的 Martin Thompson 那样运行,Store 屏障会将所有数据推送到主内存,而负载屏障会将所有数据从主内存拉到缓存/缓冲区,那么 TR2 也将保证看到 14 on x.
我不确定哪一个是正确的,或者两者都是正确的,但 Martin Thompson 所描述的只是针对 x86 架构。 JMM 不保证对 x 的更改对 TR2 可见,但 x86 实现可以。
谢谢~
【问题讨论】:
-
您不应该关心 x86 上的内存屏障。 Java 的语义和Java 内存模型是在抽象机器 上定义的。这是唯一重要的事情。 Java 运行时负责确保 抽象机器 做出的保证在运行时得到满足。
-
事实上,x86 语义(包括缓存一致性)比 jmm 要求的要强,但是如果您不从事正如 nosid 正确指出的那样,用于 x86 的 java 运行时。
-
您的担忧是有效的。 /可能/读者 2 会打印 Bar。但是,除非读取器线程之前已与内存屏障类交互并缓存了 x 的值,否则读取器 2 将打印 foo,因为它将第一次访问 x。与 write 的交互意味着对 x 的更改将是可见的。也许更有趣的测试是让 read1 和 read2 在 W1 之前和之后执行。
-
CountDownLatch引入了额外的同步。因此,如果您使用CountDownLatch确保read2在write之后执行,那么read2将始终打印"Foo"。 -
@asticx:答案是:
"Bar"显然是可能的,如果write和read2之间没有同步,则不可能,如果有同步。我想这不是你感兴趣的。所以,请提供更多你真正想知道的信息。
标签: java multithreading memory-barriers java-memory-model