什么时候不同步 read/write 变量是安全的?

When is it safe to not synchronize read/write variables?

在 Java 中,什么时候可以 "get away" 不对多个并发线程 read/write 的变量使用同步?

我读到了一些令人惊讶的并发错误:double-checked-locking and hash-maps,并且一直在共享 read/write 情况下默认使用同步,但是,我开始怀疑什么时候可以不用。

例如,我可以使用什么样的一般规则来决定什么时候 实际上 可以安全地从这样的方法中省略 synchronized

T getFoo() {
  if (this.foo == null) {
    this.foo = createFoo() // createFoo is always thread safe.
  }
  return this.foo;
}

其中:

如果 T 是像 int 这样的原语可以吗? Integer 呢?像 String 这样的简单对象呢?等等

在没有内存屏障的情况下在线程之间共享数据是不安全的。

getFoo() 的正确性取决于 foo 字段的声明。

如果 getFoo() 被多个线程调用,每个线程可能会以 T 的不同实例结束。如果没有内存屏障,一个线程的操作(比如对字段 foo 的赋值)可能永远不会对其他线程可见——永远!换句话说,如果没有同步,则无法保证一致性 "eventual" 或其他。

如果 foovolatile,它可以作为一个足够的内存屏障,然后你就会遇到多个线程可能会在短时间内获得不同的 T 的问题他们竞相创建实例的时间。

什么时候不同步 read/write 变量是安全的?

半开玩笑的回答是只有当您完全理解对底层硬件、JVM 和您的应用程序的影响时。如果可能的话,我仍然推荐这种方法,从大量阅读和实验开始。

在实践中,您应该能够使用一些常用的模式来最大程度地减少代码中 synchronized 方法或块的数量,而无需理解所述模式的所有复杂性。这不应该过多地增加您的应用程序的风险,因为即使在 synchronized 的用法周围也有一些重要的细节,如果您了解所有这些细节,您会问一个不同的问题。

不过,您的里程数可能会有所不同,特别是如果您当地的并发大师不支持上述常用模式的形式和实现。

废话少说,举个例子

  1. 使用线程安全的集合、实用程序和类型。
    • 这包括尽可能依赖 java.util.concurrent
    • 在 JDK 及其生态系统之外,还有大量用于 Java 的额外库和工具,但它们不适合 "apply now, read more later".
  2. 使用volatile
  3. 使用安全初始化和安全发布
  4. 保持数据线程本地化。
    • 有点简单,而且出人意料地经常适用。