ast*_*icx 32 java multithreading java-memory-model memory-barriers
在阅读了更多的博客/文章等之后,我现在对内存屏障之前/之后的加载/存储行为感到困惑.
以下是Doug Lea在他关于JMM的一篇澄清文章中的两个引用,它们都非常简单:
对我来说,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");
}
}
}
Run Code Online (Sandbox Code Playgroud)
比方说,我们有1个写线程TW1首先调用内存屏障的write()方法,那么我们有2个读线程TR1和TR2调用内存屏障的READ1()和读取2()method.Consider在CPU不保留订购此程序运行(86 DO保存订货为这样的情况下,其不是这种情况),根据存储器模型,将有一个StoreStore阻挡(比方说SB1)W01/W02之间,以及R11/R12和R21/R22之间2 LoadLoad屏障(让我们说RB1和RB2).
我不确定哪一个是正确的,或者两者都是正确的,但Martin Thompson所描述的仅适用于x86架构.JMM不保证更改为x对TR2可见,但x86实现确实如此.
谢谢〜
nos*_*sid 16
Doug Lea是对的.您可以在Java语言规范的第17.4.4节中找到相关部分:
[..]对易失性变量v(第8.3.1.4节)的写入与任何线程对v的所有后续读取同步(其中"后续"根据同步顺序定义).[..]
具体机器的内存模型并不重要,因为Java编程语言的语义是根据抽象机器定义的- 独立于具体机器.Java运行时环境的责任是以这种方式执行代码,它符合Java语言规范给出的保证.
关于实际问题:
read2可以打印"Bar",因为read2之前可以执行write.CountDownLatch确保在之后read2执行,则方法将永远不会打印,因为同步将删除数据争用. writeread2"Bar"CountDownLatchx独立的易变量:
是否有意义,对volatile变量的写入不会与读取任何其他volatile变量同步?
是的,这是有道理的.如果两个线程需要相互交互,它们通常必须使用相同的volatile变量才能交换信息.另一方面,如果线程使用volatile变量而不需要与所有其他线程进行交互,我们不希望为内存屏障支付费用.
这在实践中确实很重要.让我们举个例子.以下类使用volatile成员变量:
class Int {
public volatile int value;
public Int(int value) { this.value = value; }
}
Run Code Online (Sandbox Code Playgroud)
想象一下,这个类只在一个方法中本地使用.JIT编译器可以轻松检测到该对象仅在此方法中使用(转义分析).
public int deepThought() {
return new Int(42).value;
}
Run Code Online (Sandbox Code Playgroud)
根据上述规则,JIT编译器可以删除volatile读写的所有影响,因为该volatile变量不能从任何其他线程访问.
这种优化实际上存在于Java JIT编译器中: