【问题标题】:How can I use the volatile keyword in Java correctly?如何在 Java 中正确使用 volatile 关键字?
【发布时间】:2010-06-03 16:44:36
【问题描述】:

假设我有两个线程和一个对象。一个线程分配对象:

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

另一个线程使用该对象:

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

变量 myObject 是否必须声明为 volatile?我试图了解何时使用 volatile 何时不使用,这让我感到困惑。第二个线程是否有可能在其本地内存缓存中保留对旧对象的引用?如果没有,为什么不呢?

非常感谢。

【问题讨论】:

  • 您使用的是什么版本的 Java?
  • 我正在移动设备上开发。它基本上是 java 1.4.x

标签: java volatile


【解决方案1】:

我正在尝试了解何时使用 易变的,什么时候不是

您应该尽量避免使用它。请改用AtomicReference(或在适当的情况下使用另一个atomic 类)。记忆效果相同,意图更加更清晰。

我强烈建议阅读优秀的Java Concurrency in Practice 以获得更好的理解。

【讨论】:

  • 感谢您的建议。我同意我不必使用这些“低级的东西”。但是,我很想了解它在幕后是如何工作的。这本书是一个很好的建议。谢谢。
  • @Tiyoal 有关绝对细节,请参阅讨论内存模型的 JLS 的第 17.4 节:java.sun.com/docs/books/jls/third_edition/html/memory.html#17.4
  • 实践中的第二个并发。 Volatile 是一种较弱的同步形式。如果一个变量正在由 1 个线程写入并由多个线程读取以确保立即看到写入,则可以使用。
  • @Kevin: a cite from this link from @Alerty: “对变量的访问就像它包含在一个同步块中,自身同步。”,这基本上就是我在回答中想要表达的。
  • @bwawolk:这就是我的问题所在。一个线程写入,一个(多个)读取(读取)。为什么我不必将我的共享对象标记为 volatile?
【解决方案2】:

抛开复杂的技术细节,您可以将volatile 或多或少视为变量synchronized 修饰符。当您想同步对方法或块的访问时,通常希望使用synchronized 修饰符,如下所示:

public synchronized void doSomething() {}

如果您想“同步”对变量的访问,那么您想使用volatile 修饰符:

private volatile SomeObject variable;

在幕后他们做了不同的事情,但效果是相同的:更改对于下一个访问线程立即可见。

在您的具体情况下,我认为 volatile 修饰符没有任何价值。 volatile 不保证分配对象的线程将在使用该对象的线程之前运行。反过来也一样好。您可能只想先在use() 方法中进行空检查。

更新:另见this article

对变量的访问就像包含在一个同步块中,在其自身上同步。我们在第二点中说“表现得好像”,因为至少对程序员(并且可能在大多数 JVM 实现中)不涉及实际的锁定对象

【讨论】:

  • 这完全不正确。 Volatile 完全是关于变量的记忆效应和可见性,而不是并发访问。
  • 我很困惑。我一直在阅读并且我读到 volatile 通常用于在线程之间共享一个标志:一个线程将线程设置为 false,而另一个通知已设置为 false。如果布尔值未声明为 volatile,则第二个线程将始终注意到已设置为 false,因为该值可能已存储在本地线程缓存中。这不是真的还是我误解了那句话?
  • @Kevin:是的,这就是技术细节。我说的是效果,所以它对初学者来说更有意义。变量的变化对于下一个访问线程立即可见。
  • @BalusC 使用同步具有更新变量可见性的副作用,但这不是它的主要目的。我认为您支持将其作为解决方案弊大于利
  • @Kevin:我不确定它是否弊大于利。这可能取决于人们如何解释这一切。但是,我完全同意您最好寻找出色的 java.util.concurrent API,我赞成您对建议的回答。
【解决方案3】:

声明一个 volatile Java 变量意味着:

  • 此变量的值永远不会在线程本地缓存
  • 对变量的访问就像它包含在同步块中一样

volatile 的典型和最常见用法是

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

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

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

【讨论】:

    【解决方案4】:

    在这种情况下,您可以使用 volatile。您将需要围绕对变量的访问或某种类似机制(如 AtomicReference)的 volatile 同步,以保证在分配线程上所做的更改实际上对读取线程是可见的。

    【讨论】:

    • 但这如何保证分配对象的线程将在使用该对象的线程之前运行
    • 这里为什么需要同步?声明为 volatile 还不够吗?
    • @Tiyoal - 非此即彼。您需要使用这些选项之一。
    • @BalusC - 它没有,但这与线程无关,只需检查 null。没有要求说一个必须在另一个之前完成,或者它们不能被多次调用。因此,读取线程必须在每次调用时检查 null 是否被认为是可能的返回值。您引用了一个生产者/消费者场景,这将是一个完全不同的解决方案。
    【解决方案5】:

    我花了很多时间试图理解volatile 关键字。 我认为 @aleroot 给出了世界上最好、最简单的例子。

    这又是我对傻瓜的解释(比如我:-)):

    场景 1:假设 stop 没有被声明为 volatile 那么 给定的线程会做并“认为”以下内容:

    1. stopWork() 被调用:我必须将stop 设置为true
    2. 太好了,我在本地堆栈中完成了,现在我必须更新 JVM 的主堆。
    3. 糟糕,JVM 告诉我将 CPU 中的一条路让给另一个线程,我必须停止一段时间...
    4. 好的,我回来了。现在我可以用我的值更新主堆。更新中...

    场景 2:现在让 stop 声明为 volatile

    1. stopWork() 被调用:我必须将stop 设置为true
    2. 太好了,我在本地堆栈中完成了,现在我必须更新 JVM 的主堆。
    3. 对不起,伙计们,我现在必须做 (2) - 我被告知它是 volatile。我得占用 CPU 时间长一点……
    4. 正在更新主堆...
    5. 好的,我完成了。现在我可以屈服了。

    没有同步,只是一个简单的想法......

    为什么不声明所有变量volatile 以防万一?由于 Scenario2/Step3。这有点低效,但仍然比常规同步好。

    【讨论】:

      【解决方案6】:

      这里有一些令人困惑的 cmets:澄清一下,假设两个不同的线程调用 assign()use(),您的代码不正确

      在没有volatile 或其他happens-before 关系(例如,公共锁上的同步)的情况下,不能保证调用use() 的线程可以看到assign() 中对myObject 的任何写入-- 不会立即,不会及时,甚至永远不会。

      是的,volatile 是纠正此问题的一种方法(假设这是不正确的行为 - 存在您不关心此问题的合理情况!)。

      您完全正确,“使用”线程可以看到myObject 的任何“缓存”值,包括在构造时分配的值和任何中间值(同样在没有其他发生之前的点的情况下) .

      【讨论】:

        猜你喜欢
        • 2016-12-13
        • 2016-02-12
        • 2015-02-16
        • 2011-02-08
        • 2012-06-01
        • 2022-01-23
        • 1970-01-01
        • 2012-04-01
        • 1970-01-01
        相关资源
        最近更新 更多