Java与主内存的同步频率如何?

时间:2013-05-09 20:33:10

标签: java multithreading concurrency

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

如果我从另一个线程中读取这些值并且我没有用户使用volatile / atomic / synchronized这些值是如何过时的那样?

我问,因为我想知道我是否可以避免在这里使用volatile / atomic / synchronized。

我目前认为我无法对更新时间作出任何假设(所以我不得不至少使用volatile)。只是想确保我不会在这里遗漏一些东西。

4 个答案:

答案 0 :(得分:4)

价值的陈旧程度完全取决于实施的自由裁量权 - spec不提供任何保证。您将编写依赖于特定JVM的实现细节的代码,这些代码可以通过更改内存模型或JIT如何重新排序代码来解决。规范似乎是为了给实施者提供他们想要的绳索,只要他们观察到由volatile,final,synchronized等强加的约束。

答案 1 :(得分: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,不再允许进行优化。

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

答案 2 :(得分:0)

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

  1. 计算出我准备接受的最大年龄。我会做到这一点 “更新间隔”。
  2. 每个“更新间隔”将未同步的计数器变量复制到同步变量。这需要在写线程上完成。
  3. 读取线程只能读取这些同步变量。
  4. 当然,这种优化可能只是一个微小的改进,考虑到它会产生额外的复杂性,可能不值得。

答案 3 :(得分:0)

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

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

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