我需要运行很多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);
}
答案 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<>();