简体   繁体   English

如何正确使用Java中的volatile关键字?

[英]How can I use the volatile keyword in Java correctly?

Say I have two threads and an object. 说我有两个线程和一个对象。 One thread assigns the object: 一个线程分配对象:

public void assign(MyObject o) {
    myObject = o;
}

Another thread uses the object: 另一个线程使用该对象:

public void use() {
    myObject.use();
}

Does the variable myObject have to be declared as volatile? 变量myObject是否必须声明为volatile? I am trying to understand when to use volatile and when not, and this is puzzling me. 我试图了解何时使用volatile而不是什么时候,这让我感到困惑。 Is it possible that the second thread keeps a reference to an old object in its local memory cache? 是否有可能第二个线程在其本地内存缓存中保留对旧对象的引用? If not, why not? 如果没有,为什么不呢?

Thanks a lot. 非常感谢。

I am trying to understand when to use volatile and when not 我试图了解何时使用volatile而不是

You should mostly avoid using it. 你应该大多避免使用它。 Use an AtomicReference instead (or another atomic class where appropriate). 改为使用AtomicReference (或适当的其他原子类)。 The memory effects are the same and the intent is much clearer. 记忆效应是相同的,意图清晰。

I highly suggest reading the excellent Java Concurrency in Practice for a better understanding. 我强烈建议您阅读优秀的Java Concurrency in Practice以便更好地理解。

Leaving the complicated technical details behind, you can see volatile less or more as a synchronized modifier for variables . 留下复杂的技术细节,你可以看到volatile作为变量synchronized修饰符更少或更多。 When you'd like to synchronize access to methods or blocks, then you'd usually like to use the synchronized modifier as follows: 当您想要同步访问方法或块时,您通常喜欢使用synchronized修饰符,如下所示:

public synchronized void doSomething() {}

If you'd like to "synchronize" access to variables, then you'd like to use the volatile modifier: 如果您想“同步”对变量的访问,那么您希望使用volatile修饰符:

private volatile SomeObject variable;

Behind the scenes they do different things, but the effect is the same: the changes are immediately visible for the next accessing thread. 在幕后他们做了不同的事情,但效果是一样的:对于下一个访问线程,更改立即可见。

In your specific case, I don't think that the volatile modifier has any value. 在您的具体情况下,我认为volatile修饰符没有任何价值。 The volatile does not guarantee in any way that the thread assigning the object will run before the thread using the object. volatile不以任何方式保证分配对象的线程将在使用该对象的线程之前运行。 It can be as good the other way round. 反过来也可以这样好。 You probably just want to do a nullcheck in use() method first. 您可能只想在use()方法中首先执行nullcheck。

Update : also see this article : 更新 :另见本文

Access to the variable acts as though it is enclosed in a synchronized block, synchronized on itself. 对变量的访问就像包含在同步块中一样,自身同步。 We say "acts as though" in the second point, because to the programmer at least (and probably in most JVM implementations) there is no actual lock object involved . 我们在第二点说“就好像”,因为至少对程序员来说(并且可能在大多数JVM实现中) 没有涉及实际的锁对象

Declaring a volatile Java variable means: 声明一个易变的Java变量意味着:

  • The value of this variable will never be cached thread-locally 此变量的值永远不会在线程本地缓存
  • Access to the variable acts as though it is enclosed in a synchronized block 对变量的访问就像包含在同步块中一样

The typical and most common use of volatile is : volatile的典型和最常见的用途是

public class StoppableThread extends Thread {
  private volatile boolean stop = false;

  public void run() {
    while (!stop) {
      // do work 
    }
  }

  public void stopWork() {
    stop = true;
  }
}

You can use volatile in this case. 在这种情况下,您可以使用volatile。 You will require volatile, synchronization around the access to the variable or some similar mechanism (like AtomicReference) to guarantee that changes made on the assignment thread are actually visible to the reading thread. 您将需要围绕对变量或类似机制(如AtomicReference)的访问进行volatile,同步,以保证对赋值线程所做的更改实际上对读取线程可见。

I have spent quite a lot of time trying to understanding the volatile keyword. 我花了很多时间试图理解volatile关键字。 I think @aleroot has given the best and simplest example in the world. 我认为@aleroot已经给出了世界上最好,最简单的例子。

This is in turn my explanation for dummies (like me :-)): 这反过来我对假人的解释(像我:-)):

Scenario1: Assuming the stop is not declared as volatile then a given thread does and 'thinks' the following: 场景1: 假设stop没有被声明为volatile,那么给定的线程会做并“认为”以下内容:

  1. stopWork() is called: I have to set the stop to true stopWork() :我必须将stop设置为true
  2. Great, I did it in my local stack now I have to update the main heap of JVM. 太好了,我在我的本地堆栈中做到了,现在我必须更新JVM的主堆。
  3. Oops, JVM tells me to give a way in CPU to another thread, I have to stop for a while... 糟糕,JVM告诉我在CPU中给出另一个线程的方法,我必须暂停一段时间......
  4. OK, I am back. 好的,我回来了。 Now I can update the main heap with my value. 现在我可以用我的值更新主堆。 Updating ... 更新......

Scenario2: Now let the stop be declared as volatile : 场景2: 现在让stop声明为volatile

  1. stopWork() is called: I have to set the stop to true stopWork() :我必须将stop设置为true
  2. Great, I did it in my local stack now I have to update the main heap of JVM. 太好了,我在我的本地堆栈中做到了,现在我必须更新JVM的主堆。
  3. Sorry guys, I have to do (2) NOW - I am told it is volatile . 对不起伙计们,我必须做(2)现在 - 我被告知这是volatile I have to occupy CPU a bit longer... 我必须占用CPU更长一点......
  4. Updating the main heap ... 更新主堆...
  5. OK, I am done. 好的,我完成了。 Now I can yield. 现在我可以屈服了。

No synchronization, just a simple idea... 没有同步,只是一个简单的想法......

Why not to declare all variables volatile just in case? 为什么不在以防万一的情况下声明所有变量volatile Because of Scenario2/Step3. 由于Scenario2 / Step3。 It is a bit inefficient but still better than regular synchronization. 它有点低效但仍然比常规同步更好。

There are some confusing comments here: to clarify, your code is incorrect as it stands, assuming two different threads call assign() and use() . 这里有一些令人困惑的评论:澄清,你的代码是不正确的 ,假设两个不同的线程调用assign()use()

In the absence of volatile , or another happens-before relationship (for example, synchronization on a common lock) any write to myObject in assign() is not guaranteed to be seen by the thread calling use() -- not immediately, not in a timely fashion, and indeed not ever. 在缺少volatile或其他发生在之前的关系(例如,在公共锁上的同步)时,任何对assign() myObject写入都不能保证被调用use()的线程看到 - 不是立即,不是及时的,真的不是永远的。

Yes, volatile is one way of correcting this (assuming this is incorrect behaviour -- there are plausible situations where you don't care about this!). 是的, volatile是纠正这种情况的一种方法(假设这是不正确的行为 - 有可能的情况你不关心这个!)。

You are exactly correct that the 'use' thread can see any 'cached' value of myObject , including the one it was assigned at construction time and any intermediate value (again in the absence of other happens-before points). 你是完全正确的'use'线程可以看到myObject任何'缓存'值,包括它在构造时分配的值和任何中间值(再次没有其他发生前的点)。

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

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