在 Java 中,+= 运算符线程安全吗?

2022-08-31 20:35:05

我发现了以下Java代码。

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

其中 和 是 。resultpartsdouble[]

我知道基元类型的基本操作是线程安全的,但我不确定。如果上述是必要的,也许有更好的类来处理这样的操作?+=synchronized


答案 1

不。该操作不是线程安全的。它需要锁定和/或适当的“发生之前”关系链,以便任何涉及赋值到共享字段或数组元素的表达式都是线程安全的。+=

(当字段声明为 ,则存在“发生前”关系...但仅限于读取和写入操作。该操作由读取和写入组成。这些是单独的原子,但序列不是。大多数赋值表达式都涉及一次或多次读取(在右侧)和一次写入。该序列也不是原子序列。volatile+==

有关完整故事,请阅读JLS 17.4 ...或Brian Goetz等人的“Java Concurrency in Action”的相关章节。

据我所知,基元类型的基本操作是线程安全的...

实际上,这是一个不正确的前提:

  • 考虑数组的情况
  • 考虑到表达式通常由一系列操作组成,并且原子操作序列不能保证是原子的。

该类型还有一个附加问题。JLS(17.7)是这样说的:double

“出于Java编程语言内存模型的目的,对非易失性长整型或双精度值的单次写入被视为两次单独的写入:每段32位一次写入。这可能导致线程从一次写入中看到 64 位值的前 32 位,而从另一次写入中看到第二个 32 位的情况。

“易失性长值和双精度值的写入和读取始终是原子的。


在评论中,您问道:

那么我应该使用哪种类型来避免全局同步,全局同步会停止此循环内的所有线程?

在这种情况下(在更新 的情况下),除了与锁或基元互斥体同步之外,没有其他替代方法。double[]

如果你有 或 你可以用 或 替换它们,并利用这些类的无锁更新。但是没有类,甚至没有类。int[]long[]AtomicIntegerArrayAtomicLongArrayAtomicDoubleArrayAtomicDouble

(更新 - 有人指出番石榴提供了一个类,所以这将是一种选择。实际上一个好主意。AtomicDoubleArray

避免“全局锁”和大规模争用问题的一种方法可能是将数组划分为概念区域,每个区域都有自己的锁。这样,如果一个线程使用数组的相同区域,则只需阻止另一个线程。(单个写入器/多个读取器锁也可以提供帮助...如果绝大多数访问都是读取的。


答案 2

尽管在java中没有或没有,但您可以基于轻松创建自己的。AtomicDoubleAtomicDoubleArrayAtomicLongArray

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));
    }
}

如您所见,我使用和存储如.它们都具有相同的大小(以位为单位),因此精度不会丢失(除了-NaN,但我认为这并不重要)。Double.doubleToLongBitsDouble.longBitsToDoubleDoublesLongsAtomicLongArray

在Java 8中,实现可能更容易,因为您可以使用java 1.8中添加的方法。addaccumulateAndGetAtomicLongArray

Upd:看来我几乎重新实现了番石榴的AtomicDoubleArray