来自CompletableFuture执行的意外空值

时间:2019-12-06 08:38:40

标签: java

我需要运行很多o REST调用并保存结果。 为了加快速度,我想同时进行。 以下代码通常可以正常工作,但有时我在结果列表中得到空值(始终在第一个位置)。 知道为什么会这样吗?

第二个问题。 有没有更好的方法来执行此任务(并行REST调用)?

List<Result> result = new ArrayList<>();
CompletableFuture.allOf(ids.stream()
    .map(id -> asyncRestService.fetchResult(id)
        .exceptionally(e -> {
            log.error("error id: " + id, e);
            return new Result();
        })
        .thenAccept(result::add))
    .toArray(CompletableFuture<?>[]::new)
).join();

log.debug("Result: " + result);
@Async
public CompletableFuture<Result> fetchResult(String id) {
    Result result = new Result();
    // adding something to result
    return CompletableFuture.completedFuture(result);
}

2 个答案:

答案 0 :(得分:1)

我不确定这是否是代码的主要问题,但这肯定会导致您上面描述的行为。

您正在以非线程安全的方式访问result,因为ArrayList是不同步的数据结构。

想象一下,两个线程正在同时完成他们正在处理的请求。由于传递给CompletableFuture#thenAccept(Consumer<? super T>)的lambda由完成未来的线程执行,因此ArrayList#(E)函数也可能被并行调用。快速浏览ArrayList#add(E)的源代码显示:

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

由于element的类型为Object[],因此此操作不是线程安全的,并且可能导致数据争用。 size也是如此。即使我们假设对size(和element)的所有更改都可用于其他任何线程,但这仍然不起作用,因为size++是非原子操作,尽管{{1 }}的数据类型(读取和写入类型为size are atomic的变量,但是int由多个读取和写入操作组成)。

答案 1 :(得分:0)

使用线程安全列表 CopyOnWriteArrayList 尝试初始化结果

List<Label> items = new CopyOnWriteArrayList<>();