我一直在使用Java的CompletableFuture
进行异步HTTP调用,以了解它与传统的同步(从而阻塞)调用相比有多快。
结果是压倒性的:使用CompletableFuture通常可使代码执行20次HTTP调用的速度提高8倍。
数字20不是任意的:我之所以选择它,是因为我想先耗尽所有12个CPU内核,因此一旦忙碌的内核开始释放出来,它就可以开始发出新请求(如果我错了,请纠正我,但是CompletableFuture使用Runtime.getRuntime().availableProcessors()
中可用的内核数量,因此并行进行20次调用就足以用完所有可用的内核并排队等待请求。)
每次执行时,我将结果存储在给定的List中,然后在为每个创建的Future调用CompletableFuture :: join之后简单地声明此List的大小。
我面临的问题是,在大多数情况下,列表的大小在17-19之间,而不是等于20。我的测试以4:5的比率失败(这显然在计算机之间会有所不同)。
为了简单起见,我创建了以下代码(出于简单起见,并且由于这是一个Spring Project,我决定使用RestTemplate
进行HTTP调用):
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.CompletableFuture;
import java.util.stream.IntStream;
import static org.junit.Assert.*;
public class CompletableFutureTest {
private Collection<CompletableFuture<Void>> futures;
private Collection<ResponseEntity<String>> results;
private final RestTemplate restTemplate = new RestTemplate();
@Before
public void setup() {
this.futures = new ArrayList<>();
this.results = new ArrayList<>();
}
@Test
public void asyncHttpCallsTest() {
IntStream.range(0, 20).forEach(i -> {
this.futures.add(CompletableFuture.runAsync(() -> {
this.results.add(this.restTemplate.getForEntity(URI.create("http://www.google.com"), String.class));
}));
});
this.futures.forEach(CompletableFuture::join);
Assert.assertEquals(20, this.results.size());
}
}
我确信CompletableFuture
是可靠的,所以我敢打赌,由于缺乏对该API的了解,我缺少了一些东西。
谢谢!