Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/multithreading/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
是+;=Java中的运算符线程安全?_Java_Multithreading - Fatal编程技术网

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

是+;=Java中的运算符线程安全?,java,multithreading,Java,Multithreading,我发现了以下Java代码 for (int type = 0; type < typeCount; type++) synchronized(result) { result[type] += parts[type]; } } for(int type=0;type

我发现了以下Java代码

for (int type = 0; type < typeCount; type++)
    synchronized(result) {
        result[type] += parts[type];
    }
}
for(int type=0;type
其中
结果
部分
双[]


我知道基本类型上的基本操作是线程安全的,但我不确定
+=
。如果上面的
synchronized
是必需的,是否有更好的类来处理此类操作?

在32位JVM中,即使是正常的“double”数据类型也不是线程安全的(因为它不是原子的),因为它在Java中需要8个字节(涉及2*32位操作)。

否。
+=
操作不是线程安全的。对于任何涉及分配到共享字段或数组元素的表达式,它需要锁定和/或适当的“发生在”关系链,以确保线程安全

(如果字段声明为
volatile
,则存在“发生在”关系…但仅在读写操作上存在。
+=
操作由读操作和写操作组成。
+=
操作分别是原子的,但序列不是原子的。使用
=
的大多数赋值表达式都涉及一次或多次读操作(在右边)和写入。该序列也不是原子序列。)

有关完整的故事,请阅读JLS…或Brian Goetz等人撰写的“Java并发运行”的相关章节

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

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

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

double
类型还有一个附加问题。JLS()说明:

在Java编程语言内存模型中,对非易失性长值或双值的一次写入被视为两次单独的写入:每32位的一半写入一次。这可能导致线程从一次写入中看到64位值的前32位,从另一次写入中看到第二个32位

“可变长值和双值的写入和读取始终是原子的。”


在评论中,你问:

那么,我应该使用什么类型来避免全局同步,它会停止这个循环中的所有线程

在这种情况下(您正在更新一个
double[]
),除了使用锁或基本互斥体进行同步之外,别无选择

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

UPDATE-有人指出Guava提供了一个
AtomicDoubleArray
类,所以这是一个选项。实际上是一个不错的选项。)


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

正如前面所解释的,此代码不是线程安全的。避免Java-8中同步的一个可能解决方案是使用新的
双加器
类,该类能够以线程安全的方式维护两位数之和

在并行化之前创建
双加法器
对象数组:

DoubleAdder[] adders = Stream.generate(DoubleAdder::new)
                             .limit(typeCount).toArray(DoubleAdder[]::new);
然后在并行线程中累积总和,如下所示:

for(int type = 0; type < typeCount; type++) 
    adders[type].add(parts[type]);
}

尽管java中没有
AtomicDouble
AtomicDoubleArray
,但您可以基于
AtomicLongArray
轻松创建自己的

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.Double-tolongbits
Double.longBitsToDouble
AtomicLongArray
中存储
Double
作为
Longs
。它们的位大小相同,因此精度不会丢失(除了-NaN,但我认为这并不重要)

在Java 8中,
add
的实现更容易,因为您可以使用Java 1.8中添加的
AtomicLongArray
AccumerateAndget
方法


Upd:看来我实际上重新实现了guava的操作。

该操作不是原子操作,这就是为什么可能需要外部同步的原因。您还对您所指的基本操作(我假设您指的是读和写)有点困惑。原语类型(例如int)的读取确实是原子的,但这并不能使其成为线程安全的。线程安全还涉及可见性。因此,即使线程a以原子方式将整数的值设置为42,也不能保证线程B在之后执行原子读取时会看到该值。@JanusVarmarken:Re:“原语类型(例如int)的读取确实是原子的”:是的,除了long和double;请参阅。将
synchronized
放在循环中可能会有很大帮助-现在,循环的每个迭代都会进入和离开关键部分,这可能会完全支配这段代码的运行时。@assylias:这不是一个充分的理由对于原子性,实际上至少有两次读取,甚至可能更多(读取地址、读取偏移量、加载值,以及两次)。语言标准仍然可以强制要求原子性,即使对于完整表达式或完整循环也是如此。这是我第一次听说这一点。请向我解释为什么双数据类型中的线程安全依赖于JVM或OSE。您可以查看以下链接:@sharonbn@VA31此答案没有提到其他原语,更不用说
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));
    }
}