在阅读了更多博客/文章等之后,我现在对于在内存屏障之前/之后加载/存储的行为感到非常困惑。
以下是道格·利阿(Doug Lea)在其有关JMM的澄清文章中的两句话,两者都很简单:
但是当我查看关于内存障碍的另一个blog时,我得到了这些:
对我来说,Doug Lea的澄清比另一澄清更严格:基本上,这意味着如果负载障碍和存储障碍在不同的监视器上,则不能保证数据的一致性。但是后一种方法意味着即使障碍位于不同的监视器上,也可以保证数据的一致性。我不确定我是否正确理解了这2个,也不确定其中哪个是正确的。
考虑以下代码:
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()方法。请考虑该程序在不保留排序(x86)的CPU上运行对于这种情况,请务必保留排序(不是这种情况),根据内存模型,W01/W02之间将存在StoreStore屏障(假设为SB1),R11/R12和R21/R22之间将存在2个LoadLoad屏障(让我们例如RB1和RB2)。
我不确定哪一个是正确的,或者两者都正确,但是Martin Thompson描述的只是x86体系结构。 JMM不保证TR2可以看到对x的更改,但是x86实现可以。
谢谢〜
最佳答案
Doug Lea是对的。您可以在Java语言规范的§17.4.4部分中找到相关的部分:
具体机器的内存模型无关紧要,因为Java编程语言的语义是根据抽象机器定义的-与具体机器无关。 Java运行时环境有责任以这种方式执行代码,使其符合Java语言规范所提供的保证。
关于实际问题:
read2
方法可以打印"Bar"
,因为read2
可以在write
之前执行。 CountDownLatch
进行了额外的同步以确保read2
在write
之后执行,则方法read2
将永远不会输出"Bar"
,因为与CountDownLatch
的同步会删除x
上的数据竞争。 独立易失变量:
易失变量的写入与任何其他易失变量的读取不同步是否有意义?
是的,这很有道理。如果两个线程需要相互交互,则它们通常必须使用相同的
volatile
变量才能交换信息。另一方面,如果一个线程使用了volatile变量而不需要与所有其他线程进行交互,则我们不想为内存屏障付出代价。在实践中,这实际上很重要。让我们举个例子。下列类使用 volatile 成员变量:
class Int {
public volatile int value;
public Int(int value) { this.value = value; }
}
想象一下,此类仅在方法中本地使用。 JIT编译器可以轻松地检测到该对象仅在此方法中使用(Escape analysis)。
public int deepThought() {
return new Int(42).value;
}
使用上述规则,由于无法从任何其他线程访问
volatile
变量,因此JIT编译器可以删除volatile
读取和写入的所有影响。这种优化实际上存在于Java JIT编译器中:
关于java - Java中的内存屏障行为,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/24469063/