什么时候不同步读/写变量是安全的?

时间:2016-05-11 16:09:42

标签: java concurrency jvm

在Java中,什么时候可以逃脱"没有对多个并发线程的读/写变量使用synchronized?

我读到了一些令人惊讶的并发错误:double-checked-lockinghash-maps,并且在共享读/写情况下默认情况下一直使用同步,但是,我开始想知道它何时会出现问题。没关系。

例如,我可以使用哪种通用规则来确定实际何时可以安全地从这样的方法中省略synchronized

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

其中:

  • T可能是基元或任意对象
  • createFoo始终是线程安全的,可以多次调用,但未指定。
  • getFoo()可以"最终一致"。

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

2 个答案:

答案 0 :(得分:2)

什么时候不同步读/写变量是安全的?

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

在实践中,您应该能够使用一些常用模式来最小化代码中synchronized方法或块的数量,而无需了解所述模式的所有复杂性。这不应该增加你的应用程序的风险,因为即使围绕synchronized的使用,也有非平凡的细节,如果你理解了所有这些细节,你会问一个不同的问题。

尽管如此,您的里程可能会有所不同,特别是如果您所使用的常用模式的形式和实现没有得到您当地的并发大师的祝福。

足够的诽谤,给我一些例子

  1. 使用线程安全的集合,实用程序和类型。
    • 这包括尽可能依赖java.util.concurrent
    • 在JDK及其生态系统之外,还有大量额外的Java库和工具,但它们并不像“立即申请,稍后阅读”那样容易获得资格。
  2. 使用volatile
  3. 使用安全初始化和安全发布
  4. 保持数据线程本地化。
    • 有点不费脑子,而且经常适用。

答案 1 :(得分:1)

在没有内存障碍的线程之间共享数据永远不会安全。

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

如果多个线程调用getFoo(),则每个线程最终可能会使用T的不同实例。如果没有内存障碍,一个线程的操作(比如分配给字段foo)可能永远不会被其他线程看到 - 永远!换句话说,如果没有同步,则不保证一致性,“最终”或其他方式。

如果foovolatile,则会充当内存屏障,然后您就会遇到多个线程在短时间内获得不同T的问题。他们竞相创造实例。