Java 8 Streams干扰forEach在一个字段上

时间:2017-06-16 11:41:55

标签: java concurrency java-8 java-stream

使用java 8流考虑以下愚蠢的程序:

private int biggestInt;

private void run() {
    ExecutorService executor = Executors.newWorkStealingPool();

    List<Callable<Integer>> callables = new ArrayList<>();

    for (int i = 0; i<50; i++) {
        callables.add(randomInt());
    }

    try {
        executor.invokeAll(callables)
            .stream()
            .map(future -> {
                    try {
                        return future.get();
                    } catch (Exception e) {
                        throw new IllegalStateException(e);
                    }
                })
            .forEach(this::compareBiggestInt);
    } catch (InterruptedException e) { /* do nothing */ }
}

private Callable<Integer> randomInt() {
    return () -> {
        Random random = new Random(System.currentTimeMillis());
        return random.nextInt();
    };
}

private void compareBiggestInt(Integer in) {
    if (in > biggestInt)
        biggestInt = in;
}

我的问题是,forEach(this :: compareBiggestInt)是并行执行的,因此会在bigInt上引入竞争条件?

如果是这样,我怎样才能避免这种竞争条件? 我可以举例如下更改方法吗?

private synchronized void compareBiggestInt(Integer in) {[...]}

感谢任何帮助!

5 个答案:

答案 0 :(得分:2)

不,forEach不是并行执行的。这会打破forEachstream()相比parallelStream()使用时预期行为的总体合约,并且不会受到您引入{ExecutorService的影响{1}}。

invokeAll()实际上会返回List {em}已完成或已超时的Future个实例。因此,并行部分已在您完成时完成与您的流互动。

答案 1 :(得分:2)

并行流中未执行forEach。实际执行异步任务的是executorStream#map操作将一直等到所有Future完成。

IF 您希望在并行流中执行操作,您应该使用reduction operationStream#reduce。例如:

biggestInt = executor.invokeAll(callables)
        .parallelStream()
        .map(...)// same with yours
        .reduce(BinaryOperator.maxBy(Comparator.naturalOrder()))
        .orElse(null);

答案 2 :(得分:2)

这里有一些问题。第一:

return () -> {
    Random random = new Random(System.currentTimeMillis());
    return random.nextInt();
};

执行可以这么快(我可以很容易地重现),这将始终返回相同的值。

我建议您至少删除millis

private static Callable<Integer> randomInt() {
    return () -> {
        Random random = new Random();
        int x = random.nextInt(100);
        System.out.println(x);
        return x;
    };
}

甚至更好地使用ThreadLocalRandom.current().nextInt(100)

我还更改了nextInt以在[0.. 100]范围内返回,因为nextInt可能会返回一个负值并想象您返回50个负值然后您的最大值是zero的{​​{1}}(默认值);这显然是错误的。

然后您的信息流为biggestInt,并在每个sequential操作中阻止,直到map完成。所以你的Future.get由一个线程执行。

答案 3 :(得分:0)

您不使用parallel流,因此您的流是顺序的。如果您想确保您的信息流已完成,请按顺序添加.sequential()方法。

来自docs

default Stream<E> stream()  
Returns a sequential Stream with this collection as its source.

答案 4 :(得分:0)

假设您并行运行流(我将代码更改为使用“parallelStream”),您必须保护对共享可变变量的所有更改。

例如,在下面的代码中,我在方法“compareBiggestInt”中使用“synchronized”来保护对变量“largestInt”的所有访问。 (如果删除“synchronized”并运行下面的代码,您可以看到方法“compareBiggestInt”确实存在争用条件)

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


public class ParallelStreamExample  {

    private volatile int biggestInt;

    public static void main(String[] args) {
        ParallelStreamExample parallelStreamExample = new ParallelStreamExample();
        parallelStreamExample.doTheWork();
    }



    private void doTheWork() {
        ExecutorService executor = Executors.newWorkStealingPool();

        List<Callable<Integer>> callables = new ArrayList<>();

        for (int i = 0; i < 5; i++) {
            callables.add(randomInt());
        }

        try {
            executor.invokeAll(callables)
                    .parallelStream()
                    .map(future -> {
                        try {
                            return future.get();
                        } catch (Exception e) {
                            throw new IllegalStateException(e);
                        }
                    })
                    .forEach(this::compareBiggestInt);
        } catch (InterruptedException e) { /* do nothing */ }
    }

    private Callable<Integer> randomInt() {
        return () -> {
            Random random = new Random();
            return random.nextInt(10);
        };
    }

    private synchronized void compareBiggestInt(Integer in)  {
        System.out.println("in:" + in + " - current biggestint = " + biggestInt);
        if (in > biggestInt) {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            biggestInt = in;
        }
        System.out.println("in:" + in + " - current biggestint = " + biggestInt);
    }
}