否。该
+=操作不是线程安全的。对于涉及分配给共享字段或数组元素的任何表达式,它要求锁定和/或适当的“先于”关系链是线程安全的。
(在字段声明为
volatile的情况下,“先发生”关系存在…但是仅在读和写操作上存在。该
+=操作由一个读和一个写操作组成。它们分别是原子的,但是顺序不是。大多数使用的赋值表达式
=涉及一个或多个读取(在右侧)和写入。该
序列 也不是原子的。)
有关完整的故事,请阅读JLS
17.4
…或Brian Goetz等人的“ Java Concurrency in Action”的相关章节。
据我所知,基本类型的基本操作是线程安全的。
实际上,这是一个不正确的前提:
- 考虑数组的情况
- 考虑到表达式通常由一系列操作组成,并且不能保证一系列原子操作是原子操作。
该
double类型还有另一个问题。JLS(17.7)说:
“就Java编程语言内存模型而言,对非易失性long或double值的单次写入被视为两次单独的写入:一次写入每个32位的一半。这可能导致线程看到以下情况:来自一次写入的64位值的前32位,以及来自另一次写入的后32位。”
“易失的long和double值的写入和读取始终是原子的。”
在评论中,您询问:
那么我应该使用哪种类型来避免全局同步,这会停止此循环内的所有线程?
在这种情况下(如果您要更新
double[],则无法使用锁或原始互斥锁进行同步。
如果您使用
int[]或
long[],则可以用
AtomicIntegerArray或替换它们,
AtomicLongArray并利用这些类的无锁更新。但是,没有
AtomicDoubleArray课程,甚至没有
AtomicDouble课程。
( UPDATE -有人指出,番石榴提供了一个
AtomicDoubleArray类,这样 将 是一种选择一个好的实际。)
避免“全局锁定”和大量争用问题的一种方法可能是将数组划分为概念区域,每个区域都有自己的锁定。这样,一个线程仅在使用数组的同一区域时才需要阻塞另一个线程。(如果绝大多数访问是读取的,那么单作家/多读者锁定也可能会有所帮助。)



