当前位置: 首页 > 知识库问答 >
问题:

Java使用非易失性重排序易失性写入

汪飞捷
2023-03-14

我对下面的代码段有一个问题。结果可能有一个结果[0,1,0](这是用JCStress执行的测试)。那么这是怎么发生的呢?我认为数据写入(data=1)应该在Actor2(guard2=1)中写入到guard2之前执行。我说得对吗?我问,因为很多时候我读到挥发物周围的说明没有重新排序。此外,根据这一点:http://tutorials.jenkov.com/java-concurrency/volatile.html其内容如下:

JVM无法对易失性变量的读写指令进行重新排序(只要JVM检测到重新排序后的程序行为没有变化,JVM可能会出于性能原因对指令进行重新排序)。前后的指令可以重新排序,但易失性读或写不能与这些指令混合。在易变变量读或写之后的任何指令都保证在读或写之后发生。

所以如果我们不能重新排序

  public class DoubleVolatileTest {

      volatile int guard1 = 0;
      int data = 0;
      volatile int guard2 = 0;

      @Actor
      public void actor1() {
          guard2 = 1;
          data = 1;
          guard1 = 1;
      }

      @Actor
      public void actor2(III_Result r) {
          r.r1 = guard1;
          r.r2 = data;
          r.r3 = guard2;
      }

  }

提前感谢!

共有1个答案

吕高昂
2023-03-14

首先,这:

JVM无法对易失性变量的读写指令进行重新排序。。。

意味着挥发物本身不能重新排序(即挥发物与挥发物);但要注意的是

只要JVM在重新排序过程中没有检测到程序行为的变化,JVM就可以出于性能原因重新排序指令。

一般来说,关于JVM重新排序(可能或不可能)的推理是不正确的(我已经读到关于挥发物的指令没有重新排序…)。重新订购/屏障/等不属于JLS的一部分;相反,它是在规则之前发生的,这是您唯一应该关心的事情。

您的示例,确实可以简化为评论中所说:

@Outcome(id = "0, 0", expect = Expect.ACCEPTABLE, desc = "don't care about this one")
@Outcome(id = "1, 0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "the one we care about")
@Outcome(id = "1, 1", expect = Expect.ACCEPTABLE, desc = "don't care about this one")
@Outcome(id = "0, 1", expect = Expect.ACCEPTABLE, desc = "don't care about this one")
@JCStressTest
@State
public class VolatileTest {


    private volatile int guard = 0;
    private int x = 0;


    @Actor
    void writeActor() {
        guard = 1; // volatile store

        // your reasoning is that these two operations should be re-ordered
        // unfortunately, this is not correct.

        x = 1; // plain store
    }

    @Actor
    void readActor(II_Result r) {

        r.r1 = x; // plain store
        r.r2 = guard; // plain store

    }
}

运行此操作将导致1,0,这意味着x=1确实是用guard=1重新订购的;事实上,可能会发生更多其他事情(但为了简单起见,我们称之为重新排序,尽管这不是您可以观察到的唯一原因)。

用JLS术语来说:您之前没有确定这些操作之间发生过任何事情(例如典型的易失性存储/易失性加载)-因此这些操作可以自由浮动。这可能是答案的结尾,差不多。更广泛的解释是易失性(因为您使用了它),据说:

对易失性字段的写入发生在同一字段的每次后续读取之前。

您没有对易失性保护进行任何读取,因此无法保证任何事情。另一种解释它的方法是这篇优秀的文章,甚至是这篇文章。但是即使您确实阅读了Guard,仍然无法保证重新排序,因为您的代码设置方式。

只有当存在成对的用法时,volatile才能正常工作,即线程1写入volatile字段-Thread2观察写入。在这种情况下,在写入程序顺序之前所做的一切都将被Thread2看到(显然是在看到写入值之后)。或在代码中:

 public class VolatileTest {

    private volatile int guard = 0;
    private int x = 0;


    @Actor
    void writeActor() {

        // store comes "before" the store to volatile
        // as opposed to the previous example
        x = 1; // plain store
        guard = 1; // volatile store
    }

    @Actor
    void readActor(II_Result r) {

        r.r1 = guard; // plain store
        r.r2 = x; // plain store

    }
}

现在,JLS向您保证,如果您看到防护罩为1,您也会看到x为1(这次不能在下方重新订购)。因此,1,0现在是非法的,因此从未在输出中看到过。

 类似资料:
  • a)为什么除了“00”之外还有其他输出? b)如何修改代码以便始终打印“00”。 对于a)我的回答是:在没有任何volatile/同步构造的情况下,编译器可以重新排序一些指令。特别是“this.initialint=val;”和“this.flag=true;”可以切换,这样就可以发生这种情况:线程都启动了,t1充电在前面。给定重新排序的指令,它首先设置flag=true。在它到达“this.in

  • 在阅读了这个问题和这个(尤其是第二个答案)之后,我对volatile及其关于记忆障碍的语义感到非常困惑。 在上面的例子中,我们写入一个易失性变量,这会导致一个mitch,这反过来会将所有挂起的存储缓冲区/加载缓冲区刷新到主缓存,使其他缓存行无效。 然而,非易失性字段可以优化并存储在寄存器中,例如?那么,我们如何才能确保给定一个写入易失性变量之前的所有状态变化都是可见的呢?如果我们有1000件东西呢

  • 对易失性字段的写和读分别防止了在易失性字段之前和之后的读/写的重新排序。在写到易失性变量之前的变量读/写不能被重新排序为在它之后发生,在从易失性变量读到之后的读/写不能被重新排序为在它之前发生。但是这种禁止的范围是什么呢?正如我所理解的,volatile变量只能在使用它的块内防止重新排序,对吗? 为了清楚起见,让我举一个具体的例子。假设我们有这样的代码: 让我再举一个具体的例子来说明范围以澄清事情

  • 如果是,如果被添加到数组中,强制转换会受到什么影响?谢谢你! 编辑:@cacahuete Frito链接了一个非常相似的问题:带有'volatile'数组的'memcpy((void*)dest,src,n)'安全吗?

  • 来自C/C++,我对Java中的volatile对象行为有点困惑。 null faik,volatile意味着b引用的“book对象”应该在主内存中。编译器可能在内部实现引用作为指针,因此b指针可能位于缓存中。我的理解是,volatile是对象的限定符,而不是引用/指针的限定符。 问题是:在使用方法中,本地引用不是易失性的。这个“本地”引用会不会把底层的Book对象从主存带到缓存中,实质上使对象不

  • 问题内容: 我尝试了解为什么此示例是正确同步的程序: 由于存在冲突的访问(存在对a的写入和读取),因此在每个顺序一致性中,必须在访问之间的关系之前执行。假设顺序执行之一: 是1发生-在2之前发生,为什么? 问题答案: 不,在相同变量的易失性写入之前(以同步顺序),在易失性写入 之前 不一定 会发生 易失性读取。 这意味着它们可能处于“数据争用”中,因为它们“冲突的访问未按先发生后关系进行排序”。如