java - LongAccumulator 没有得到正确的结果

标签 java multithreading concurrency java-8

代码优先:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.LongAccumulator;
import java.util.function.LongBinaryOperator;
import java.util.stream.IntStream;

/**
 * Created by tom on 17-4-13.
 */
public class ForSOF {
    public static void main(String[] args){
        LongBinaryOperator op = (v, y) -> (v*2 + y);
        LongAccumulator accumulator = new LongAccumulator(op, 1L);

        ExecutorService executor = Executors.newFixedThreadPool(2);

        IntStream.range(0, 10)
                .forEach(i -> executor.submit(() -> accumulator.accumulate(i)));

        stop(executor);

        // 2539 expected, however result does not always be! I had got 2037 before.
        System.out.println(accumulator.getThenReset());
    }

    /**
     * codes for stop the executor, it's insignificant for my issue.
     */
    public static void stop(ExecutorService executor) {
        try {
            executor.shutdown();
            executor.awaitTermination(60, TimeUnit.SECONDS);
        }
        catch (InterruptedException e) {
            System.err.println("termination interrupted");
        }
        finally {
            if (!executor.isTerminated()) {
                System.err.println("killing non-finished tasks");
            }
            executor.shutdownNow();
        }
    }
}

以上是我在测试 Java 8 的 LongAccumulator 类时崩溃的全部代码。来自博客的代码 winterbe.com有关 java8 的精彩旅游文章。

正如API文档所说,java.util.concurrent.atomic包中那些类的方法将是原子的和线程安全的。但是结果肯定是当我运行上面的那些时。 2037 或 1244 的某些时间错误。一定有问题。

当我将主体内的第一行重写为:

LongBinaryOperator op = (v, y) -> (v + y);

它以正确的方式工作。

所以,我猜想当 JVM 获取 v 并将其加倍时,其他线程中断并获取相同的 v 值,计算 op 表达式并给出一个结果,该结果很快就会被预线程重写。这就是 expre v+y 起作用的原因。

是否应该改进这些代码中的任何地方? 还是 LongAccumulator 类的错误? 我不确定。


天哪!这些代码的逻辑应该是这样的:

public void test(){
    int v = 1;
    for (int i = 0; i < 10; i++) {
        v = x(v, i);
    }
    System.out.println(v);
}

int x(int v, int y){
    return v*2+y;
}

v 的输出是 2037。

所以让我困惑的一定是计算流的紊乱。

最佳答案

As API document said, method of those class in package java.util.concurrent.atomic will be atomic and thread-safe. However result was certainly when I run those above. Some time error with 2037 or 1244. There must be something wrong.

LongAccumulator docs提供一条关于此的重要信息。

The order of accumulation within or across threads is not guaranteed and cannot be depended upon, so this class is only applicable to functions for which the order of accumulation does not matter.

你的问题是 v * 2 + y 操作是 noncommutative .如果你混合操作的顺序,你可以获得各种各样的输出。 v + y 起作用的原因是您可以按任何顺序应用它并获得相同的结果。

要将其分解为串行代码,您实际上是在做:

List<Long> list = new ArrayList<>();
for (long i = 0; i < 10; i++) {
    list.add(i);
}
// if you uncomment this, you get varying results
// Collections.shuffle(list);
long total = 1;
for (Long i : list) {
    total = total * 2 + i;
}
System.out.println(total);

顺序结果是 2037,但如果您取消注释 Collections.shuffle(...),那么您会看到结果变化最大为 9218,因为数字是相反的订单。

关于java - LongAccumulator 没有得到正确的结果,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43390429/

相关文章:

Java junit.framework.AssertionFailedError 错误

java - 如何使对话框始终位于最前面?

amazon-web-services - 如何使用s3cmd多线程?

应用 java AtomicIntegeraccumulateAndGet

java - 根据输入找到最小的整数

java - Android Java 编译器的源代码可用吗?

c# - 挂起 Serial.Close(),可能是线程问题?

java - 通过不带Thread.sleep()的Runtime.getRuntime()。exec解密文件

java - 是否可以使用 JPA 版本控制/乐观锁定来防止旧数据被提交修改?

java - 为什么必须从同步块(synchronized block)/方法调用等待和通知?