Java 何时不同步读/写变量是安全的?
在Java中,对于多个并发线程读/写的变量,什么时候可以不使用synchronized而“逃脱”呢 我读到了一些令人惊讶的并发错误:而且,在共享读/写案例中总是默认使用同步,但是,我开始怀疑什么时候可以不使用 例如,我可以使用什么样的一般规则来决定从以下方法中省略Java 何时不同步读/写变量是安全的?,java,concurrency,jvm,Java,Concurrency,Jvm,在Java中,对于多个并发线程读/写的变量,什么时候可以不使用synchronized而“逃脱”呢 我读到了一些令人惊讶的并发错误:而且,在共享读/写案例中总是默认使用同步,但是,我开始怀疑什么时候可以不使用 例如,我可以使用什么样的一般规则来决定从以下方法中省略synchronized实际上是安全的: T getFoo() { if (this.foo == null) { this.foo = createFoo() // createFoo is always thread s
synchronized
实际上是安全的:
T getFoo() {
if (this.foo == null) {
this.foo = createFoo() // createFoo is always thread safe.
}
return this.foo;
}
其中:
可能是原语或任意对象T
始终是线程安全的,可以多次调用,但在其他方面未指定createFoo
可以是“最终一致的”getFoo()
如果T是像
int
这样的原语,可以吗?那整数呢?像字符串这样的简单对象呢?等等。在没有内存障碍的线程之间共享数据永远都不安全
getFoo()
的正确性取决于foo
字段的声明
如果多个线程调用了getFoo()
,那么每个线程都可能以不同的T
实例结束。如果没有内存障碍,一个线程执行的操作(如分配给字段foo
)可能永远不会被其他线程看到!换句话说,如果没有同步,就不能保证一致性、“最终”或其他
如果
foo
是volatile
,这是一个足够的内存屏障,那么您就会遇到一个问题,即多个线程可能会在短时间内获得不同的T
,因为它们竞相创建实例。什么时候不同步读/写变量是安全的?
只有当您完全理解底层硬件、JVM和应用程序的含义时,才可以做出开玩笑的回答。如果可能的话,我仍然推荐这种方法,它从大量阅读和实验开始
实际上,您应该能够使用一些常用的模式来最小化代码中的同步的方法或块的数量,而不必理解所述模式的所有复杂性。这不应该增加应用程序的风险太多,因为即使是在使用synchronized
时,也有很多琐碎的细节,如果您了解所有这些细节,您可能会提出不同的问题
不过,您的里程可能会有所不同,特别是如果所述常用模式的形式和实现没有得到本地并发专家的支持
说得够多了,给我举几个例子
使用线程安全的集合、实用程序和类型。
- 这包括尽可能依赖
java.util.concurrent
- 在JDK及其生态系统之外,还有大量额外的Java库和工具,但它们并不适合“立即应用,稍后阅读”
使用volatile
- 您可以使用
volatile
变量查看一些常见模式
使用安全初始化和安全发布
- 你可以看一看什么是比初学者更高级的解释
- 我还将使用不可变类型放在这里
保持数据线程本地。
- 这是一种不需要动脑筋的方法,而且出人意料地经常适用
不这样做永远都不好,除非你能在语义上保证你不会同时读写(或者如果你不关心你的一些工作人员在更新后是否读错了东西),否则无法保证在没有适当同步的情况下更新后,任何线程都会看到该值(synchronized
是一种常见的方法,但不是唯一的方法)。线程安全不仅仅是交错的指令/时间表。@RickyMutschlechner,除非没有进一步的上下文,否则无法保证“最终一致性”;虽然人们可以沿着volatile
的路线走,但我觉得这个问题缺少了重要的上下文。。最终一致与否。@Richardlevasser不,绝对不不是。相反,没有保证(注意我是如何一直强调这一点的?)当其他线程看到该字段的任何更新时-可以是立即更新,也可以是从不更新。也就是说,变量的更改。volatile
添加了一个额外的保证,如中所述。如果您知道自己在做什么,这是可以的。例如,查看java.lang.String哈希字段,用于哈希代码兑现。如果不可见,则计算该字段-so你可能最终会从多个线程计算它,但在正常情况下,你只需要做一次。没有必要在每次访问上浪费内存障碍,以节省0.00001%的cpu,以免在其他线程中无法立即看到值。回答得很好。我维护了几个任务/数据开源并行服务,并保留统计数据变量可以是简单的int或long,没有易失性或锁定。但是,我有明确的意见如何“保护”这些变量,如果需要的话。