Java中的+ =运算符是线程安全的吗?
我find了下面的Java代码。
for (int type = 0; type < typeCount; type++) synchronized(result) { result[type] += parts[type]; } }
result
和parts
是double[]
。
我知道原始types的基本操作是线程安全的,但我不知道+=
。 如果上面的synchronized
是必要的,那么有没有更好的类来处理这种操作?
不可以。 +=
操作不是线程安全的。 它需要locking和/或一个适当的“发生之前”关系链,涉及任何涉及到共享字段或数组元素的线程安全的expression式。
(有一个被声明为volatile
的字段,“happen-before”关系存在……但仅限于读写操作, +=
操作由读和写组成,它们分别是primefaces的,但是序列不是。大多数赋值expression式使用=
包含一个或多个读取(在右边)和一个写入,这个序列也不是primefaces的。
有关完整的故事,请阅读JLS 17.4或Brian Goetz等人撰写的“Java并发实践”相关章节。
据我所知基本types的基本操作是线程安全的…
其实这是一个不正确的前提:
- 考虑数组的情况
- 认为expression式通常由一系列操作组成,并且一系列的primefaces操作不能保证是primefaces操作。
double
types还有一个额外的问题。 JLS( 17.7 )说:
“对于Java编程语言内存模型来说,对非易失性long或double值的单次写入被视为两次分开的写入:每个32位一次写入,这可能导致线程看到来自一个写入的64位值的第一个32位,以及来自另一个写入的第二个32位。
“写入和读取volatile和long值总是primefaces的。”
在一个评论中,你问:
那么,我应该使用什么types来避免全局同步,这会停止循环内的所有线程?
在这种情况下(你正在更新一个double[]
,除了用锁或原语互斥体进行同步外,别无select。
如果你有一个int[]
或一个long[]
你可以用AtomicIntegerArray
或AtomicLongArray
replace它们,并利用这些类的无锁更新。 但是没有AtomicDoubleArray
类,甚至没有AtomicDouble
类。
( 更新 – 有人指出,番石榴提供了一个AtomicDoubleArray
类,所以这将是一个选项,实际上是一个好的。
避免“全局locking”和大规模争用问题的一种方法可能是将arrays划分为名义区域,每个区域都有自己的locking。 这样,一个线程只需要阻塞另一个线程,如果他们正在使用数组的相同区域。 (如果绝大多数访问是读取,那么单个写入器/多个读取器锁也可以帮助太多。
尽pipe在java中没有AtomicDouble
或AtomicDoubleArray
,你可以很容易地基于AtomicLongArray
创build自己的。
static class AtomicDoubleArray { private final AtomicLongArray inner; public AtomicDoubleArray(int length) { inner = new AtomicLongArray(length); } public int length() { return inner.length(); } public double get(int i) { return Double.longBitsToDouble(inner.get(i)); } public void set(int i, double newValue) { inner.set(i, Double.doubleToLongBits(newValue)); } public void add(int i, double delta) { long prevLong, nextLong; do { prevLong = inner.get(i); nextLong = Double.doubleToLongBits(Double.longBitsToDouble(prevLong) + delta); } while (!inner.compareAndSet(i, prevLong, nextLong)); } }
正如你所看到的,我使用Double.doubleToLongBits
和Double.longBitsToDouble
在AtomicLongArray
存储Doubles
AtomicLongArray
。 它们都具有相同的位数,所以精度不会丢失(除了-NaN,但我认为这不重要)。
在Java 8中, add
的实现可以更容易,因为您可以使用在Java 1.8中添加的AtomicLongArray
accumulateAndGet
方法。
更新 :看来,我几乎重新实施番石榴的AtomicDoubleArray 。
即使是普通的“double”数据types在32位JVM中也不是线程安全的(因为它不是primefaces的),因为它在Java中需要8个字节(涉及2 * 32位操作)。
正如已经解释过的,这段代码不是线程安全的。 在Java-8中避免同步的一个可能的解决scheme是使用新的DoubleAdder
类,它能够以线程安全的方式保持双数的总和。
在并行化之前创buildDoubleAdder
对象的数组:
DoubleAdder[] adders = Stream.generate(DoubleAdder::new) .limit(typeCount).toArray(DoubleAdder[]::new);
然后在这样的并行线程中累加和:
for(int type = 0; type < typeCount; type++) adders[type].add(parts[type]); }
最后在并行子任务完成后得到结果:
double[] result = Arrays.stream(adders).mapToDouble(DoubleAdder::sum).toArray();