简体   繁体   English

在java中易失性

[英]Volatile in java

As far as I know volatile write happens-before volatile read , so we always will see the freshest data in volatile variable. 据我所知,在 易失性读取 之前发生了 易失性写入 ,所以我们总是会在volatile变量中看到最新的数据。 My question basically concerns the term happens-before and where does it take place? 我的问题基本上涉及到之前发生的术语以及它发生在哪里? I wrote a piece of code to clarify my question. 我写了一段代码来澄清我的问题。

class Test {
   volatile int a;
   public static void main(String ... args) {
     final Test t = new Test();
     new Thread(new Runnable(){
        @Override
        public void run() {
            Thread.sleep(3000);
            t.a = 10;
        }
     }).start();
     new Thread(new Runnable(){
        @Override
        public void run() {
            System.out.println("Value " + t.a);
        }
     }).start();
   }
}

(try catch block is omitted for clarity) (为清楚起见,省略了try catch块)

In this case I always see the value 0 to be printed on console. 在这种情况下,我总是看到要在控制台上打印的值0。 Without Thread.sleep(3000); 没有Thread.sleep(3000); i always see value 10. Is this a case of happens-before relationship or it prints 'value 10' because thread 1 starts a bit earlier thread 2? 我总是看到值10.这是发生在之前关系的情况还是打印'值10',因为线程1开始早一点线程2?

It would be great to see the example where the behaviour of code with and without volatile variable differs in every program start, because the result of code above depends only(at least in my case) on the order of threads and on thread sleeping. 很高兴看到每个程序启动时带有和不带volatile变量的代码行为都不同的例子,因为上面代码的结果仅取决于(至少在我的情况下)线程顺序和线程休眠。

You see the value 0 because the read is executed before the write. 您会看到值0,因为在写入之前执行了读取操作。 And you see the value 10 because the write is executed before the read. 并且您看到值10,因为写入在读取之前执行。

If you want to have a test with more unpredictable output, you should have both of your threads await a CountDownLatch, to make them start concurrently: 如果您希望测试具有更多不可预测的输出,则应该让两个线程等待CountDownLatch,以使它们同时启动:

final CountDownLatch latch = new CountDownLatch(1);
new Thread(new Runnable(){
    @Override
    public void run() {
        try {
            latch.await();
            t.a = 10;
        }
        catch (InterruptedException e) {
            // end the thread
        }
    }
 }).start();
 new Thread(new Runnable(){
    @Override
    public void run() {
        try {
            latch.await();
            System.out.println("Value " + t.a);
        }
        catch (InterruptedException e) {
            // end the thread
        }
    }
 }).start();
 Thread.sleep(321); // go
 latch.countDown();

The happens-before really has to do with a write happens before any subsequent read. 之前发生的事情与写入在任何后续读取之前发生有关。 If the write has not occurred yet there really is no relationship. 如果写没有发生,那么真的没有关系。 Since the write-thread is sleeping the read is executed before the write occurs. 由于写线程处于休眠状态,因此在写入发生之前执行读取。

To observe the relationship in action you can have two variables one that is volatile and one that is not. 要观察行动中的关系,你可以有两个变量,一个是volatile,另一个是volatile。 According to the JMM it says the write to a non-volatile variable before a volatile write happens before the volatile read. 根据JMM,它表示在易失性写入发生之前,在易失性读取之前写入非易失性变量。

For instance 例如

volatile int a = 0;
int b = 0;

Thread 1: 线程1:

b = 10;
a = 1;

Thread 2: 线程2:

while(a != 1);
if(b != 10)
  throw new IllegalStateException();

The Java Memory Model says that b should always equal 10 because the non-volatile store occurs before the volatile store. Java内存模型说b应该总是等于10,因为非易失性存储发生在volatile存储之前。 And all writes that occur in one thread before a volatile store happen-before all subsequent volatile loads. 并且在易失性存储发生之前发生在一个线程中的所有写入 - 在所有后续的易失性加载之前。

don't stick to the term 'happens-before'. 不要坚持“发生在之前”这个词。 it is a relation between events, used by jvm during R/W operations scheduling. 它是事件之间的关系,由jvm在R / W操作调度期间使用。 at this stage it won't help you understand the volatile. 在这个阶段,它不会帮助你了解不稳定性。 the point is: jvm orders all R/W operations. 重点是:jvm命令所有R / W操作。 jvm can order however it wants (of course obeying to all synchronize, lock, wait etc). jvm可以订购但它想要(当然服从所有同步,锁定,等待等)。 and now: if variable is volatile then any read operation will see the result of latest write operation. 现在:如果变量是易失性的,那么任何读操作都将看到最新写操作的结果。 if variable is not volatile then it is not guaranteed (in different threads). 如果变量不是volatile,那么就不能保证(在不同的线程中)。 that's all 就这样

I've re-phrased (changes in bold fonts) the happens-before rule mentioned in the first sentence of your question as below so that it could be understood better - 我已经重新措辞(粗体字的变化)你问题的第一句中提到的发生前规则,如下所示,以便更好地理解 -

"write of the value of a volatile variable to the main memory happens-before any subsequent read of that varible from main memory ". “将易失性变量的值写入主存储器 - 在从主存储器中随后读取该变量之前”。

  • Also it is important to note that volatile writes/reads always happen to/from the main memory and NOT to/from any local memory resources like registers, processor caches etc. 另外需要注意的是, 易失性写入/读取总是发生在主存储器中,而不是发送到/来自寄存器,处理器缓存等任何本地存储器资源。

The practical implication of the above happens-before rule is that all the threads that share a volatile variable will always see consistent value of that variable. 上述发生之前的实际含义是, 所有共享volatile变量的线程总是会看到该变量的一致值 No two threads see different values of that variable at any given point of time. 在任何给定的时间点,没有两个线程看到该变量的不同值。

On the contrary, all the threads that share a non-volatile variable may see different values at any given point of time unless it is not synchronized by any other kind of synchronization mechanisms such as synchronized block/method, final keyword etc. 相反, 共享非易失性变量的所有线程可能在任何给定的时间点看到不同的值,除非它不被任何其他类型的同步机制同步,例如synchronized块/方法,最终关键字等。

Now coming back to your question on this happens-before rule, i think u've slightly misunderstood that rule. 现在回到你关于这个发生前的规则的问题,我想你有点误解了这条规则。 The rule does not dictate that a write code should always happen (execute) before a read code. 该规则并未规定写代码应始终在读代码之前发生(执行)。 Rather it dictates that if a write code (volatile variable write) were to be executed in one thread before a read code in another thread then the effect of the write code should have happened in the main memory before the read code is executed so that the read code can see the latest value. 相反,它决定了如果写入代码(挥发性可变写入)是在一个线程之前在另一个线程然后的写入代码的效果应在主存储器已经发生的读码被执行之前所读取的代码,使得所执行的读取代码可以看到最新的值。

In the absence of volatile (or any other synchronization mechanisms), this happens-before is not mandatory, and hence a reader thread might see a stale value of non-volatile variable even though it has been recently written by a different writer thread. 在没有volatile(或任何其他同步机制)的情况下,这种情况发生 - 之前不是强制性的,因此读者线程可能会看到非易失性变量的陈旧值,即使它最近由不同的编写器线程写入。 Because the writer thread can store the value in its local copy and need not have flushed the value to the main memory. 因为writer线程可以将值存储在其本地副本中,并且不需要将值刷新到主内存。

Hope the above explanation is clear :) 希望以上解释清楚:)

piotrek is right, here is the test: piotrek是对的,这是测试:

class Test {
   volatile int a = 0;
   public static void main(String ... args) {
     final Test t = new Test();
     new Thread(new Runnable(){
        @Override
        public void run() {
            try {
                Thread.sleep(3000);
            } catch (Exception e) {}
            t.a = 10;
            System.out.println("now t.a == 10");
        }
     }).start();
     new Thread(new Runnable(){
        @Override
        public void run() {
            while(t.a == 0) {}
            System.out.println("Loop done: " + t.a);
        }
     }).start();
   }
}

with volatile: it will always end 挥发性:它将永远结束

without volatile: it will never end 没有不稳定:永远不会结束

From wiki: 来自维基:

In Java specifically, a happens-before relationship is a guarantee that memory written to by statement A is visible to statement B, that is, that statement A completes its write before statement B starts its read. 特别是在Java中,发生在之前的关系是保证由语句A写入的内存对语句B可见,即语句A在语句B开始读取之前完成其写入。

So if thread A write ta with value 10 and thread B tries to read ta some later, happens-before relationship guarantees that thread B must read value 10 written by thread A, not any other value. 因此,如果线程A用值10写入ta并且线程B稍后尝试读取ta,则发生之前关系保证线程B必须读取线程A写入的值10,而不是任何其他值。 It's natural, just like Alice buys milk and put them into fridge then Bob opens fridge and sees the milk. 这很自然,就像爱丽丝购买牛奶并将它们放入冰箱一样,然后鲍勃打开冰箱看到牛奶。 However, when computer is running, memory access usually doesn't access memory directly, that's too slow. 但是,当计算机运行时,内存访问通常不会直接访问内存,这太慢了。 Instead, software get the data from register or cache to save time. 相反,软件从寄存器或缓存中获取数据以节省时间。 It loads data from memory only when cache miss happens. 它仅在发生缓存未命中时才从内存加载数据。 That the problem happens. 问题发生了。

Let's see the code in the question: 让我们看看问题中的代码:

class Test {
  volatile int a;
  public static void main(String ... args) {
    final Test t = new Test();
    new Thread(new Runnable(){ //thread A
      @Override
      public void run() {
        Thread.sleep(3000);
        t.a = 10;
      }
    }).start();
    new Thread(new Runnable(){ //thread B
      @Override
      public void run() {
        System.out.println("Value " + t.a);
      }
    }).start();
  }
}

Thread A writes 10 into value ta and thread B tries to read it out. 线程A将10写入值ta,线程B尝试将其读出。 Suppose thread A writes before thread B reads, then when thread B reads it will load the value from the memory because it doesn't cache the value in register or cache so it always get 10 written by thread A. And if thread A writes after thread B reads, thread B reads initial value (0). 假设线程A在线程B读取之前写入,然后当线程B读取它时将从内存加载值,因为它不会将值缓存在寄存器或缓存中,因此它总是由线程A写入10。如果线程A写入之后线程B读取,线程B读取初始值(0)。 So this example doesn't show how volatile works and the difference. 所以这个例子没有说明易失性是如何起作用的。 But if we change the code like this: 但是,如果我们改变这样的代码:

class Test {
  volatile int a;
  public static void main(String ... args) {
    final Test t = new Test();
    new Thread(new Runnable(){ //thread A
      @Override
      public void run() {
        Thread.sleep(3000);
        t.a = 10;
      }
    }).start();
    new Thread(new Runnable(){ //thread B
      @Override
      public void run() {
        while (1) {
          System.out.println("Value " + t.a);
        }
      }
    }).start();
  }
}

Without volatile , the print value should always be initial value (0) even some read happens after thread A writes 10 into ta, which violate the happen-before relationship. 如果没有volatile ,打印值应该始终是初始值(0),即使在线程A将10写入ta之后发生了一些读取,这违反了之前发生的关系。 The reason is compiler optimizes the code and save the ta into register and every time it will use the register value instead of reading from cache memory, of course which much faster. 原因是编译器优化代码并将ta保存到寄存器中,每次使用寄存器值而不是从高速缓冲存储器读取时,当然要快得多。 But it also cause the happen-before relationship violation problem because thread B can't get the right value after others update it. 但它也会导致发生之前的关系违规问题,因为线程B在其他人更新之后无法获得正确的值。

In the above example, volatile write happens-before volatile read means that with volatile thread B will get the right value of ta once after thread A update it. 在上面的例子中, 易失性写入发生 - 在易失性读取之前意味着在线程A更新之后, 易失性线程B将获得ta的正确值。 Compiler will guarantee every time thread B reads ta, it must read from cache or memory instead of just using register's stale value. 编译器将保证每次线程B读取ta时,它必须从缓存或内存中读取而不是仅使用寄存器的陈旧值。

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

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