首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Java中内存屏障的行为

Java中内存屏障的行为
EN

Stack Overflow用户
提问于 2014-06-28 16:58:31
回答 2查看 9.1K关注 0票数 35

在阅读了更多的博客/文章等之后,我现在对内存障碍之前/之后加载/存储的行为感到非常困惑。

下面是Doug在他的一篇关于JMM的澄清文章中引用的两段话,这两篇文章都非常直截了当:

  1. 线程A写入易失性字段f时可见的任何内容,在线程B读取f时都是可见的。
  2. 请注意,两个线程都必须访问相同的易失性变量,以便正确设置发生之前的关系。线程A写入易失性字段f时可见的所有内容在读取易失性字段g后对线程B都是可见的,这不是这样的情况。

但是,当我查看另一个关于内存障碍的blog时,我得到了以下内容:

  1. 在x86上的一个存储屏障,“sfence”指令,强制在屏障之前的所有存储指令发生在屏障之前,并让存储缓冲区被冲到缓存以缓存其发出的CPU。
  2. 一个负载屏障,x86上的“lfence”指令,强制在屏障之后发生所有加载指令,然后等待负载缓冲区为该CPU耗尽。

对我来说,Doug的澄清比另一个更加严格:基本上,这意味着如果负载屏障和存储屏障在不同的监视器上,数据一致性将得不到保证。但后者意味着即使在不同的监视器上设置了障碍,数据的一致性也将得到保证。我不确定我是否正确地理解了这2条,也不确定其中哪一条是正确的。

考虑到下列守则:

代码语言:javascript
复制
  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");
      }
    }
  }

假设我们有一个写线程TW1首先调用MemoryBarrier的写()方法,然后我们有两个读取器线程TR1和TR2调用MemoryBarrier的read1()和read2() method.Consider这个程序运行在不保持顺序的CPU上(x86确实保留这种情况下的排序),根据内存模型,W01/W02之间将有一个StoreStore屏障(例如SB1),以及R11/R12和R21/R22之间的2 LoadLoad屏障(假设RB1和RB2)。

  1. 因为SB1和RB1在同一个监视器i上,所以调用read1的线程TR1总是在x上看到14,所以也总是打印"Foo“。
  2. SB1和RB2在不同的显示器上,如果Doug是正确的,线程TR2将不能保证在x上看到14,这意味着"Bar“可能偶尔被打印出来。但是,如果像blog中描述的Martin那样运行内存屏障,存储屏障将将所有数据推送到主内存,加载屏障将将所有数据从主内存中提取到缓存/缓冲区,那么TR2也将保证在x上看到14条数据。

我不确定哪一个是正确的,或者两者都是正确的,但是Martin所描述的只是x86体系结构。JMM不能保证对x的更改对TR2是可见的,但是x86实现是可见的。

谢谢~

EN

回答 2

Stack Overflow用户

发布于 2014-06-28 18:33:12

道格·利亚是对的。您可以在Java语言规范的§17.4.4部分找到相关的部分

§17.4.4 Synchronization Order 。。对易失性变量v (§8.3.1.4)的写入与任何线程对v的所有后续读取同步(其中“后继”根据同步顺序定义)。。。

具体机器的内存模型并不重要,因为Java编程语言的语义是用抽象机器定义的--与具体机器无关。Java运行时环境有责任以这样的方式执行代码,使其符合Java语言规范提供的保证。

关于实际问题的

  • 如果没有进一步的同步,read2方法可以打印"Bar",因为read2可以在write之前执行。
  • 如果使用CountDownLatch进行额外的同步以确保read2write之后执行,那么方法read2将永远不会打印"Bar",因为与CountDownLatch的同步删除了x上的数据竞争。

独立易失变量:

对易失性变量的写入与任何其他易失性变量的读取不同步,这是否合理?

是的,这很有道理。如果两个线程需要相互交互,它们通常必须使用相同的volatile变量来交换信息。另一方面,如果线程使用易失性变量而不需要与所有其他线程交互,我们不想为内存屏障付出代价。

实际上,这在实践中是很重要的。我们来举个例子吧。以下类使用一个易失性成员变量:

代码语言:javascript
复制
class Int {
    public volatile int value;
    public Int(int value) { this.value = value; }
}

假设这个类仅在方法中本地使用。JIT编译器可以很容易地检测到,该对象仅在此方法(Escape analysis)中使用。

代码语言:javascript
复制
public int deepThought() {
    return new Int(42).value;
}

根据上述规则,JIT编译器可以删除volatile读写的所有效果,因为不能从任何其他线程访问volatile变量。

这种优化实际上存在于Java JIT编译器中:

  • src/share/vm/opto/memnode.cpp
票数 18
EN

Stack Overflow用户

发布于 2014-06-28 19:02:37

据我所知,这个问题实际上是关于易失性读/写及其发生-在保证之前。说到这部分,我只想补充诺西德的回答一件事:

在正常写入之前不能移动易失性写入,在正常读取之后不能移动易失性读。这就是为什么read1()read2()的结果将如nosid所写的那样。

说到障碍--这个定义对我来说听起来不错,但可能让你感到困惑的是,这些东西/工具/方式/机制(不管你喜欢什么都可以)来实现在hotspot中用JMM描述的行为。在使用Java时,您应该依赖于JMM保证,而不是实现细节。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/24469063

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档