简体   繁体   English

Java用非易失性对易失性写进行重新排序

[英]Java reordering volatile write with non-volatile

I have a question about the code segment bellow. 我对以下代码段有疑问。 It is possible to have a result [0, 1, 0] for the result (this is test executed with JCStress). 结果可能有结果[0,1,0](这是使用JCStress执行的测试)。 So how this can happen? 那么这怎么可能呢? I think that the data write (data = 1) should be executed before write to guard2 in Actor2 (guard2 = 1). 我认为应该在对Actor2(guard2 = 1)写入guard2之前执行数据写入(data = 1)。 Am I right? 我对吗? I ask, because a lot of times I've read that instructions arround volatiles are not reordered. 我问,因为很多时候我都读过关于挥发物的指令没有重新排序。 Moreover according to this: http://tutorials.jenkov.com/java-concurrency/volatile.html it is written the following: 而且据此: http : //tutorials.jenkov.com/java-concurrency/volatile.html编写如下:

The reading and writing instructions of volatile variables cannot be reordered by the JVM (the JVM may reorder instructions for performance reasons as long as the JVM detects no change in program behaviour from the reordering). JVM不能对易失变量的读写指令进行重新排序(出于性能原因,JVM可以对指令进行重新排序,只要JVM从重新排序中检测到程序行为没有变化)。 Instructions before and after can be reordered, but the volatile read or write cannot be mixed with these instructions. 前后的指令可以重新排序,但易失性读或写操作不能与这些指令混合使用。 Whatever instructions follow a read or write of a volatile variable are guaranteed to happen after the read or write. 无论在读取或写入易失性变量之后执行的任何指令,均应确保在读取或写入之后发生。

So if we cannot reorder 所以如果我们不能重新排序

  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;
      }

  }

Thanks in advance! 提前致谢!

First of all, this: 首先,这是:

The reading and writing instructions of volatile variables cannot be reordered by the JVM... 可变变量的读写指令无法由JVM重新排序...

means that volatile themselves can not be re-ordered (volatile with volatile that is); 意味着volatile 本身不能重新排序(volatile就是volatile); but with the caveat that 但需要注意的是

the JVM may reorder instructions for performance reasons as long as the JVM detects no change in program behaviour from the reordering. 只要JVM从重新排序中检测到程序行为没有变化,JVM可能会出于性能原因对指令进行重新排序。

In general, reasoning about re-orderings (that could or not be done) by a JVM is incorrect ( I've read that instructions around volatiles are not reordered ...). 通常,关于JVM重新排序(可能完成或无法完成)的推理是不正确的( 我读过关于volatile的指令未重新排序 ...)。 Re-ordering/barriers/etc are not part of the JLS ; 重新订购/障碍/等不属于JLS一部分; instead it acts on the premises of happens-before rules and that is the only thing you should care about. 相反,它是在happens-before规则的前提下起作用的,这是您唯一需要关心的事情。

Your example, can indeed be simplified as said in the comments to: 您的示例确实可以如注释中所述简化为:

@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

    }
}

Running this will result in 1, 0 indeed, which means that x = 1 was indeed re-ordered with guard = 1 ; 运行此命令实际上将得到1, 0 ,这意味着x = 1的确被重新排列为guard = 1 actually in reality many more other things could have happened (but for simplicity we call them re-orderings, even though this is not the only reason why you could observe [1, 0] ). 实际上,实际上还有更多其他事情可能发生(但为简单起见,我们称它们为重排序,尽管这不是您可以观察到[1, 0]的唯一原因)。

In JLS terms : you have not established any happens before between these operations (like a typical volatile store/volatile load) - as such those operations are free to float around. 用JLS术语来说 :在这些操作之间您没有建立任何事件 (例如典型的易失性存储/易失性负载),因此,这些操作可以随意浮动。 And that could be the end of the answer, pretty much. 那几乎是答案的终点。 A little bit broader explanation would be that volatile (since you used it), is said: 可以说,更广泛的解释是该volatile (自从使用以来):

A write to a volatile field happens-before every subsequent read of that same field. 在每次后续读取同一字段之前,都会对易失字段进行写操作。

You are not doing any reads of the volatile guard , so nothing is guaranteed. 您没有对volatile guard进行任何读取,因此无法保证。 Another way to explain it would be this excellent article or even this one . 解释它的另一种方式是这个优秀的文章 ,甚至这一个 But even if you did read guard , still nothing is guaranteed about re-orderings because the way your code is set-up. 但是,即使您确实读过guard ,也仍然无法保证重新排序,因为代码的设置方式。


volatile only works correctly when there are pairs of its usage, that is Thread1 does a write to a volatile field - Thread2 observes the write. volatile时,有对它的用法,也就是只有正常工作Thread1做一个写volatile场- Thread2观察写。 In this case everything that was done before the write in program order will be seen by Thread2 (obviously after it has seen that written value). 在这种情况下,按程序顺序写入之前完成的所有操作都将由Thread2看到(显然,在看到该写入值之后)。 Or in code: 或在代码中:

 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

    }
}

You are now guaranteed by the JLS that if you see guard to be 1 , you will also observe x to be 1 ( x = 1 can not be re-ordered below guard = 1 this time). 现在, JLS保证您可以看到,如果guard1 ,那么您还将观察到x1 (这次不能将x = 1重新排列为guard = 1以下)。 As such, 1, 0 now is illegal and thus never seen in the output. 因此1, 0现在是非法的,因此从输出中看不到。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM