简体   繁体   English

深入理解Java中的volatile

[英]Deep understanding of volatile in Java

Does Java allows output 1, 0 ? Java 允许输出1, 0吗? I've tested it very intensively and I cannot get that output.我已经对其进行了非常深入的测试,但无法获得该输出。 I get only 1, 1 or 0, 0 or 0, 1 .我只得到1, 10, 00, 1

public class Main {
    private int x;
    private volatile int g;

    // Executed by thread #1
    public void actor1(){
       x = 1;
       g = 1;
    }

    // Executed by thread #2
    public void actor2(){
       put_on_screen_without_sync(g);
       put_on_screen_without_sync(x);
    }
}

Why?为什么?

On my eye it is possible to get 1, 0 .在我看来,有可能得到1, 0 My reasoning.我的推理。 g is volatile so it causes that memory order will be ensured. g是易失性的,因此会确保内存顺序。 So, it looks like:所以,它看起来像:

actor1:

(1) store(x, 1)
(2) store(g, 1)
(3) memory_barrier // on x86

and, I see the following situation: reorder store(g, 1) before store(x,1) (memory_barrier is after (2)).并且,我看到以下情况:在store(x,1) store(g, 1)重新排序(memory_barrier(2) 之后)。 Now, run thread #2.现在,运行线程#2。 So, g = 1, x = 0 .所以, g = 1, x = 0 Now, we have expected output.现在,我们有了预期的输出。 What is incorrect in my reasoning?我的推理有什么不正确的地方?

Any actions before a volatile write happen before (HB) any subsequent volatile read of the same variable.易失性写入之前的任何操作都发生在 (HB) 同一变量的任何后续易失性读取之前。 In your case, the write to x happens before the write to g (due to program order).在您的情况下,写入x发生在写入g之前(由于程序顺序)。

So there are only three possibilities:所以只有三种可能:

  • actor2 runs first and x and g are 0 - output is 0,0 actor2 首先运行并且 x 和 g 为 0 - 输出为 0,0
  • actor1 runs first and x and g are 1 because of the happens before relationship HB - output is 1,1 actor1 首先运行并且 x 和 g 是 1 因为 happens before 关系 HB - 输出是 1,1
  • the methods run concurrently and only x=1 is executed (not g=1 ) and the output could be either 0,1 or 0,0 (no volatile write so no guarantee)这些方法同时运行并且只执行x=1 (不是g=1 )并且输出可以是 0,1 或 0,0 (没有易失性写入所以不能保证)

No, this isn't possible.不,这是不可能的。 According to the JMM, anything that was visible to thread 1 when it writes to a volatile field becomes visible to thread 2 when it reads that field.根据 JMM,线程 1 在写入可变字段时可见的任何内容在线程 2 读取该字段时都变得可见。

There is another example similar to yours provided here : 此处提供了另一个类似于您的示例:

class VolatileExample {
  int x = 0;
  volatile boolean v = false;
  public void writer() {
    x = 42;
    v = true;
  }

  public void reader() {
    if (v == true) {
      //uses x - guaranteed to see 42.
    }
  }
}

You will never see 1, 0 , but properly explaining this is not going to be easy, spec wise.您永远不会看到1, 0 ,但正确地解释这并不容易,具体而言。 First let's get some obvious things out of the door.首先让我们把一些明显的东西拿出来。 The specification says :规范

If x and y are actions of the same thread and x comes before y in program order , then hb(x, y).如果 x 和 y 是同一个线程的动作,并且 x 在程序顺序中出现在 y 之前,则 hb(x, y)。

This means that on the writing thread side, hb(x, g) and on the reading side hb(g, x) .这意味着在写入线程端hb(x, g)读取hb(g, x) But this is only so, if you would have to reason about each thread individually , as the chapter about Program order says: :但仅此而已,如果你必须单独推理每个线程,正如关于Program order的章节所说

Among all the inter-thread actions performed by each thread t...在每个线程执行的所有线程操作中...

So if you imagine running each thread at a time, then happens-before would be correct for each of them, individually.因此,如果您想象一次运行每个线程,那么happens-before对每个线程都是正确的。 But you don't.但你没有。 Your actors (I am sure you use jcstress there) run concurrently.您的演员(我确定您在那里使用jcstress )同时运行。 So relying on "program order" for reasoning is not enough (neither it is correct).所以依靠“程序顺序”来推理是不够的(也不正确)。

You need to somehow synchronize these two actions now - the reading and the writing .您现在需要以某种方式同步这两个操作 -阅读写作 And here is how the specification says it can be done :以下是规范所说的如何完成

A write to a volatile variable synchronizes-with all subsequent reads of v by any thread (where "subsequent" is defined according to the synchronization order).对 volatile 变量的写入与任何线程对 v 的所有后续读取同步(其中“后续”是根据同步顺序定义的)。

And later says: 后来说:

If an action x synchronizes-with a following action y, then we also have hb(x, y).如果动作 x与后续动作 y 同步,那么我们也有 hb(x, y)。

If you put all of these together now:如果你现在把所有这些放在一起:

          (hb)              (hb)             (hb)
write(x) ------> write(g) -------> read(g) -------> read(x)

This is also called to "transitively" close program order and synchronizes-with order .这也被称为“传递地”关闭program ordersynchronizes-with order Since there is hb on every step, seeing 1, 0 (a racy read), is impossible according to the spec.由于每一步都有hb ,因此根据规范不可能看到1, 0 (活泼的读取)。

No, and in fact this property of volatile is used in classes like ConcurrentHashMap to implement a lock-free happy path, roughly like this:不,事实上volatile的这个属性在ConcurrentHashMap这样的类中用来实现无锁快乐路径,大致是这样的:

volatile int locked = 0;
...
void mutate() {
    if (Unsafe.compareAndSwapInt(locked,0,1)) { 
    /*this isn't exactly how you call this method, but the point stands: 
      if we read 0, we atomically replace it with 1 and continue on the happy 
      path */
       //we are happy
       //so we mutate the structure and then
       locked = 0;           
    } else {
       //contended lock, we aren't happy
    }
}

Since writes before a volatile write can't be reordered after the volatile write, and reads after volatile read can't be reordered before the volatile read, code like this indeed works as a "lockless locking".由于在易失性写入之前的写入不能在易失性写入之后重新排序,并且易失性读取之后的读取不能在易失性读取之前重新排序,所以这样的代码确实可以作为“无锁锁定”。

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

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