3

我有一组计数器,它们只会在一个线程中更新。

如果我从另一个线程读取这些值并且我不使用 volatile/atomic/synchronized 这些值会过时吗?

我问我是否可以避免在这里使用 volatile/atomic/synchronized。

我目前认为我无法对更新时间做出任何假设(因此我被迫至少使用 volatile)。只是想确保我在这里没有遗漏任何东西。

4

4 回答 4

4

我问我是否可以避免在这里使用 volatile/atomic/synchronized。

在实践中,CPU 缓存可能会定期同步到主内存(多久取决于许多参数),所以听起来你会不时看到一些新值。

但这没有抓住重点:实际问题是,如果您不使用正确的同步模式,编译器可以自由地“优化”您的代码并删除更新部分。

例如:

class Broken {
    boolean stop = false;

    void broken() throws Exception {
        while (!stop) {
            Thread.sleep(100);
        }
    }
}

编译器被授权将该代码重写为:

void broken() throws Exception {
    while (true) {
        Thread.sleep(100);
    }
}

stop因为没有义务在您执行该broken方法时检查非易失性是否可能发生变化。stop将变量标记为volatile,不再允许优化。

底线:如果您需要共享状态,则需要同步。

于 2013-05-09T22:21:03.260 回答
4

值的陈旧程度完全取决于实现的自由裁量权——规范不提供任何保证。您将编写的代码取决于特定 JVM 的实现细节,并且可能会因内存模型的更改或 JIT 对代码的重新排序方式的更改而被破坏。编写规范的目的似乎是为实现者提供尽可能多的绳索,只要他们遵守 volatile、final、synchronized 等施加的约束。

于 2013-05-09T21:12:20.057 回答
0

Java8 有一个名为 LongAdder 的新类,它有助于解决在字段上使用 volatile 的问题。但在那之前...

如果您不在计数器上使用 volatile,那么结果是不可预测的。如果你确实使用了 volatile ,那么就会出现性能问题,因为每次写入都必须保证缓存/内存的一致性。当有很多线程频繁写入时,这是一个巨大的性能问题。

对于对应用程序不重要的统计信息和计数器,我为用户提供了 volatile/atomic 或 none 选项,默认为 none。到目前为止,大多数都没有使用。

于 2013-05-10T13:30:41.770 回答
0

看起来我可以避免这些变量同步的唯一方法是执行以下操作(类似于 Zan Lynx 在评论中建议的):

  1. 算出我准备接受的最大年龄。我会将其设为“更新间隔”。
  2. 每个“更新间隔”将未同步的计数器变量复制到同步变量。这需要在写线程上完成。
  3. 读取线程只能从这些同步变量中读取。

当然,这种优化可能只是边际改进,考虑到它会产生额外的复杂性,可能不值得。

于 2013-05-09T23:01:25.217 回答