Java中的+ =运算符是线程安全的吗?

我find了下面的Java代码。

for (int type = 0; type < typeCount; type++) synchronized(result) { result[type] += parts[type]; } } 

resultpartsdouble[]

我知道原始types的基本操作是线程安全的,但我不知道+= 。 如果上面的synchronized是必要的,那么有没有更好的类来处理这种操作?

不可以。 +=操作不是线程安全的。 它需要locking和/或一个适当的“发生之前”关系链,涉及任何涉及到共享字段或数组元素的线程安全的expression式。

(有一个被声明为volatile的字段,“happen-before”关系存在……但仅限于读写操作, +=操作由读和写组成,它们分别是primefaces的,但是序列不是。大多数赋值expression式使用=包含一个或多个读取(在右边)和一个写入,这个序列也不是primefaces的。

有关完整的故事,请阅读JLS 17.4或Brian Goetz等人撰写的“Java并发实践”相关章节。

据我所知基本types的基本操作是线程安全的…

其实这是一个不正确的前提:

  • 考虑数组的情况
  • 认为expression式通常由一系列操作组成,并且一系列的primefaces操作不能保证是primefaces操作。

doubletypes还有一个额外的问题。 JLS( 17.7 )说:

“对于Java编程语言内存模型来说,对非易失性long或double值的单次写入被视为两次分开的写入:每个32位一次写入,这可能导致线程看到来自一个写入的64位值的第一个32位,以及来自另一个写入的第二个32位。

“写入和读取volatile和long值总是primefaces的。”


在一个评论中,你问:

那么,我应该使用什么types来避免全局同步,这会停止循环内的所有线程?

在这种情况下(你正在更新一个double[] ,除了用锁或原语互斥体进行同步外,别无select。

如果你有一个int[]或一个long[]你可以用AtomicIntegerArrayAtomicLongArrayreplace它们,并利用这些类的无锁更新。 但是没有AtomicDoubleArray类,甚至没有AtomicDouble类。

更新 – 有人指出,番石榴提供了一个AtomicDoubleArray类,所以这是一个选项,实际上是一个好的。

避免“全局locking”和大规模争用问题的一种方法可能是将arrays划分为名义区域,每个区域都有自己的locking。 这样,一个线程只需要阻塞另一个线程,如果他们正在使用数组的相同区域。 (如果绝大多数访问是读取,那么单个写入器/多个读取器锁也可以帮助太多。

尽pipe在java中没有AtomicDoubleAtomicDoubleArray ,你可以很容易地基于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.doubleToLongBitsDouble.longBitsToDoubleAtomicLongArray存储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();