什么时候不同步 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
可能是基元或任意对象
createFoo
始终是线程安全的,可以多次调用,但未指定。
getFoo()
可以是 "eventually consistent".
如果 T 是像 int
这样的原语可以吗? Integer
呢?像 String 这样的简单对象呢?等等
在没有内存屏障的情况下在线程之间共享数据是不安全的。
您 getFoo()
的正确性取决于 foo
字段的声明。
如果 getFoo()
被多个线程调用,每个线程可能会以 T
的不同实例结束。如果没有内存屏障,一个线程的操作(比如对字段 foo
的赋值)可能永远不会对其他线程可见——永远!换句话说,如果没有同步,则无法保证一致性 "eventual" 或其他。
如果 foo
是 volatile
,它可以作为一个足够的内存屏障,然后你就会遇到多个线程可能会在短时间内获得不同的 T
的问题他们竞相创建实例的时间。
什么时候不同步 read/write 变量是安全的?
半开玩笑的回答是只有当您完全理解对底层硬件、JVM 和您的应用程序的影响时。如果可能的话,我仍然推荐这种方法,从大量阅读和实验开始。
在实践中,您应该能够使用一些常用的模式来最大程度地减少代码中 synchronized
方法或块的数量,而无需理解所述模式的所有复杂性。这不应该过多地增加您的应用程序的风险,因为即使在 synchronized
的用法周围也有一些重要的细节,如果您了解所有这些细节,您会问一个不同的问题。
不过,您的里程数可能会有所不同,特别是如果您当地的并发大师不支持上述常用模式的形式和实现。
废话少说,举个例子
- 使用线程安全的集合、实用程序和类型。
- 这包括尽可能依赖
java.util.concurrent
。
- 在 JDK 及其生态系统之外,还有大量用于 Java 的额外库和工具,但它们不适合 "apply now, read more later".
- 使用
volatile
- 您可以查看 http://www.ibm.com/developerworks/library/j-jtp06197/ 使用
volatile
变量的一些常见模式。
- 使用安全初始化和安全发布
- 您可以查看 http://shipilev.net/blog/2014/safe-public-construction/ 以获得比适合初学者的更高级的解释。
- 我还会在这里使用不可变类型。
- 保持数据线程本地化。
- 有点简单,而且出人意料地经常适用。
在 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
可能是基元或任意对象createFoo
始终是线程安全的,可以多次调用,但未指定。getFoo()
可以是 "eventually consistent".
如果 T 是像 int
这样的原语可以吗? Integer
呢?像 String 这样的简单对象呢?等等
在没有内存屏障的情况下在线程之间共享数据是不安全的。
您 getFoo()
的正确性取决于 foo
字段的声明。
如果 getFoo()
被多个线程调用,每个线程可能会以 T
的不同实例结束。如果没有内存屏障,一个线程的操作(比如对字段 foo
的赋值)可能永远不会对其他线程可见——永远!换句话说,如果没有同步,则无法保证一致性 "eventual" 或其他。
如果 foo
是 volatile
,它可以作为一个足够的内存屏障,然后你就会遇到多个线程可能会在短时间内获得不同的 T
的问题他们竞相创建实例的时间。
什么时候不同步 read/write 变量是安全的?
半开玩笑的回答是只有当您完全理解对底层硬件、JVM 和您的应用程序的影响时。如果可能的话,我仍然推荐这种方法,从大量阅读和实验开始。
在实践中,您应该能够使用一些常用的模式来最大程度地减少代码中 synchronized
方法或块的数量,而无需理解所述模式的所有复杂性。这不应该过多地增加您的应用程序的风险,因为即使在 synchronized
的用法周围也有一些重要的细节,如果您了解所有这些细节,您会问一个不同的问题。
不过,您的里程数可能会有所不同,特别是如果您当地的并发大师不支持上述常用模式的形式和实现。
废话少说,举个例子
- 使用线程安全的集合、实用程序和类型。
- 这包括尽可能依赖
java.util.concurrent
。 - 在 JDK 及其生态系统之外,还有大量用于 Java 的额外库和工具,但它们不适合 "apply now, read more later".
- 这包括尽可能依赖
- 使用
volatile
- 您可以查看 http://www.ibm.com/developerworks/library/j-jtp06197/ 使用
volatile
变量的一些常见模式。
- 您可以查看 http://www.ibm.com/developerworks/library/j-jtp06197/ 使用
- 使用安全初始化和安全发布
- 您可以查看 http://shipilev.net/blog/2014/safe-public-construction/ 以获得比适合初学者的更高级的解释。
- 我还会在这里使用不可变类型。
- 保持数据线程本地化。
- 有点简单,而且出人意料地经常适用。