【问题标题】:Is the volatile keyword required for "non-concurrent multithreading"?“非并发多线程”是否需要 volatile 关键字?
【发布时间】:2014-07-06 04:38:17
【问题描述】:

我有一个由多个线程使用的对象,但从不同时使用(总是只有一个线程在其上执行一个方法)。它包含几个非最终字段。现在我想知道是否必须将所有字段标记为 volatile 以确保下一个线程(之前可能使用过该对象)将看到更改。如果没有 volatile,一个线程在什么时候从另一个线程获取更改?有保证吗?

如果我必须使用 volatile,Akka 是如何解决这个问题的?

在幕后,Akka 将在真实场景中运行一组演员 线程,通常许多参与者共享一个线程,然后 一个参与者的调用可能最终在不同的地方处理 线程。 Akka 确保此实现细节不会影响 处理actor状态的单线程。

【问题讨论】:

  • 请澄清I have an object which is used by multiple threads, but never concurrently.
  • @SotiriosDelimanolis,现在清楚了吗?
  • @Joel 不太清楚。你的意思是:(a)多个线程读取并且只有一个线程写入? (b) 多线程读写,但没有竞态条件; (c) 只有一个特定线程读取或写入。我猜你的意思是“(b)”。
  • @necromancer, (d) 多线程读写,但始终保证只有一个线程可以访问对象。您可以将其与同步锁进行比较,但没有实际的锁。
  • 你们如何确保保证?

标签: java multithreading akka volatile


【解决方案1】:

如果happens-before 关系在对象外建立,没有需要使成员变量volatile。即使没有synchronized 块,也可以建立happens-before 关系。下面的例子使用volatile 来达到这个目的——保证写入者和读取者不能同时访问同一个对象。在这种情况下,可以保证阅读器读取到正确的值。

class Foobar {
    public static volatile Foobar instance = new Foobar(42);
    public int value;
    public Foobar(int value) { this.value = value; }
}

int reader() {
    return Foobar.instance.value;
}

void writer(int value) {
    Foobar.instance = new Foobar(value);
}

所以,有趣的部分是对象外部的代码——保证只有一个线程可以访问该对象。可以编写代码来保证它而不建立 happens-before 关系。但是,这会很奇怪,您应该在那里解决问题,而不是使成员变量volatile

【讨论】:

  • Happens-before 关系不保证线程间的一致性,见this
  • @alfasin java 保证顺序一致性,而不仅仅是发生之前的一致性,只要没有数据竞争。如果您继续阅读,您会看到“示例 17.4.8-1。在一致性不足之前发生”,它解释了为什么为一致性不足而建立的约束条件。
  • @alfasin:我不明白你的评论。您能否更详细地描述您遇到的问题?
  • @alfasin “我的意思是,happens-before 仅在同一个线程内保证顺序一致性”这是错误的。只要您没有任何数据竞争,jmm 就可以保证全局的顺序一致性!
  • @alfasin:您可以在 Java 语言规范§17.4.3§17.4.5 中找到它。后者指出:“[..] 如果所有顺序一致的执行都没有数据竞争 [..],那么程序的所有执行都将看起来是顺序一致的(第 17.4.3 节)。”
【解决方案2】:

如果您可以确保一个线程和一个线程仅尝试同时访问该对象,那么可以,将变量声明为volatile 仍然是必要的,因为它可以防止每个线程保存变量的“本地副本”并更新它随意。

阅读this article 并注意可见性部分,它会让您更清楚地了解 volatile 的含义。

【讨论】:

  • 这就是我所担心的,但是我真的很想知道 Akka 是如何解决这个问题的。你能想出一个没有锁的解决方案吗?还是他们只是使用锁而很少更改线程以确保锁不会太昂贵?
  • 我没有使用 Akka,但我知道它采用了基于“一切都是演员”的思想的 Actor 模型。演员在他们之间传递消息,这些消息是不可变的——所以不需要锁定。这些消息的发送也是异步完成的,这意味着需要更深入地了解它如何处理并发问题。
【解决方案3】:

如果没有 volatile,线程可能会更改缓存中的对象,volatile 会使线程将更改刷新到内存中。

【讨论】: