Java使用非易失性

时间:2017-11-04 14:58:22

标签: java concurrency volatile

我对下面的代码段有疑问。结果可能有[0,1,0](这是用JCStress执行的测试)。那怎么会发生这种情况呢?我认为数据写入(data = 1)应该在写入Actor2中的guard2之前执行(guard2 = 1)。我对吗?我问,因为很多时候我已经阅读过指令,不会重新排序挥发物。而且根据这个:http://tutorials.jenkov.com/java-concurrency/volatile.html它写成如下:

  

JVM无法对volatile变量的读写指令进行重新排序(只要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 个答案:

答案 0 :(得分:1)

首先,这是

  

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

表示不能重新排列易失性本身(易失性与易失性);但要注意的是

  

由于性能原因,JVM可能会重新排序指令,只要JVM从重新排序中未发现程序行为发生变化即可。

通常,JVM关于重新排序(可能完成或无法完成)的推理是不正确的(我已阅读有关挥发物的指令未重新排序 ... )。重新排序/障碍/等不属于JLS的一部分;相反,它仅在happens-before规则的前提下起作用,这是您唯一需要关心的事情。

您的示例确实可以如以下注释中所述简化:

@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重新排序;实际上,实际上还有更多其他事情可能发生(但为简单起见,我们称它们为重排序,尽管这不是观察[1, 0]的唯一原因)。

用JLS术语:您尚未在这些操作之间建立任何发生(例如典型的易失性存储/易失性负载),因此这些操作可以自由浮动周围。那几乎是答案的终点。更广泛的解释是volatile(因为您使用了它)被这样说:

  

对易失性字段的写操作发生在每次对该字段的后续读取之前。

您没有对volatile guard进行任何读取,因此无法保证。另一种解释方式是this excellent article甚至是this one。但是,即使您确实阅读了guard,由于代码的设置方式,仍然无法保证重新排序。


volatile仅在有成对用法时才正确工作,即Thread1volatile字段执行写操作-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向您保证,如果您看到guard1,那么您还将观察到x1({{这次无法在x = 1以下重新排序1}}。因此,guard = 1现在是非法的,因此从输出中看不到。