场景如下:一个用户有N张不同银行卡,要求一个接口展示这N张卡的余额。如果各种银行查询余额API平均响应时间为2秒,假设用户有5张卡,就需要2*5=10秒才能展示出来结果
改造为异步处理理论上就只需要2秒,实际情况中,Completablefuture改造之后,最慢的那个API响应即为整个结果的响应时间。但用户卡数量是动态的,所以异步任务也是动态。

创建模拟查询代码

假设四大行为A、B、C、D。其中A银行API响应为2秒、B银行API响应为3秒、C银行API响应为4秒、D银行API响应为5秒。余额统一设为100

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
private static int getBankA (String cardNo) {
System.out.println("查询A银行-2秒");
try {
// 模拟API调用耗时
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 100;
}

private static int getBankB (String cardNo) {
System.out.println("查询B银行-3秒");
try {
// 模拟API调用耗时
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 100;
}

private static int getBankC (String cardNo) {
System.out.println("查询C银行-4秒");
try {
// 模拟API调用耗时
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 100;
}

private static int getBankD (String cardNo) {
System.out.println("查询D银行-5秒");
try {
// 模拟API调用耗时
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 100;
}

创建业务查询结果展示对象

1
2
3
4
5
6
7
8
9
10
11
@Data
@AllArgsConstructor
public class CardInfo {
// 银行名称
private String bank;
// 银行卡号
private String cardNo;
// 余额
private int money;

}

统一银行余额查询方法,路由各银行查询API封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static CardInfo queryCardInfo(String bank, String cardNo){
int money = 0;
// 四大行API路由
switch (bank) {
case "A" :
money = getBankA(cardNo);
break;
case "B" :
money = getBankB(cardNo);
break;
case "C" :
money = getBankC(cardNo);
break;
case "D" :
money = getBankD(cardNo);
break;
default:
System.out.println("未知银行");
}
return new CardInfo(bank, cardNo, money);
}

模拟同步调用情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void main(String[] args) {
// 模拟数据库,用户 - 卡号,银行
HashMap<String, List<String>> DB = new HashMap<String, List<String>>();
DB.put("张三", Arrays.asList("1111,A", "2222,B", "3333,C", "4444,D"));
DB.put("李四", Arrays.asList("11110,A", "22220,B", "33330,B", "44440,D"));

long start = System.currentTimeMillis();

// 首先查询数据库获得用户卡号的归属银行
List<String> cardList = DB.get("张三");
List<CardInfo> result = new ArrayList<>();
for (String info : cardList) {
String[] split = info.split(",");
CardInfo cardInfo = queryCardInfo(split[1], split[0]);
result.add(cardInfo);
}
System.out.println("展示结果集:" + JSONUtil.parse(result));

long time = System.currentTimeMillis() - start;
System.out.println("耗时 " + time);
}

可以看到执行结果就是累加时间 14023 毫秒

1
2
3
4
5
6
查询A银行-2秒
查询B银行-3秒
查询C银行-4秒
查询D银行-5秒
展示结果集:[{"bank":"A","money":100,"cardNo":"1111"},{"bank":"B","money":100,"cardNo":"2222"},{"bank":"C","money":100,"cardNo":"3333"},{"bank":"D","money":100,"cardNo":"4444"}]
耗时 14023

改造为使用Completablefuture

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public static void main(String[] args) {
// 模拟数据库,用户 - 卡号,银行
HashMap<String, List<String>> DB = new HashMap<String, List<String>>();
DB.put("张三", Arrays.asList("1111,A", "2222,B", "3333,C", "4444,D"));
DB.put("李四", Arrays.asList("11110,A", "22220,B", "33330,B", "44440,D"));

long start = System.currentTimeMillis();

// 首先查询数据库获得用户卡号的归属银行
List<String> cardList = DB.get("张三");
List<CardInfo> result = new ArrayList<>();
// 使用线程池 PS:当不传递Executor时,会使用ForkJoinPool中的共用线程池CommonPool(CommonPool的大小是CPU核数-1,如果是IO密集的应用,线程数可能成为瓶颈)
ExecutorService threadPool = Executors.newFixedThreadPool(5);
// 得到需要执行的任务列表
List<CompletableFuture<CardInfo>> futureList = cardList.stream().map(info -> {
String[] split = info.split(",");
return CompletableFuture.supplyAsync(() -> queryCardInfo(split[1], split[0]), threadPool);
}).collect(Collectors.toList());

// allOf 代表所有依赖的任务执行完成,才会结束
CompletableFuture allFuture = CompletableFuture.allOf(futureList.toArray(new CompletableFuture[futureList.size()]));

// 任务都执行完成就将结果聚合返回
allFuture.thenAccept(o ->
futureList.forEach(job -> {
try {
result.add(job.get());
} catch (Exception e) {
e.printStackTrace();
}
})
);
allFuture.join();
System.out.println("展示结果集:" + JSONUtil.parse(result));

long time = System.currentTimeMillis() - start;
System.out.println("耗时 " + time);
}

可以看到执行结果就是最慢的D银行的时间 5059毫秒。说明任务是同步进行的,结果集依赖所有任务执行完成,所以响应时间就是最慢的D银行API 5秒

1
2
3
4
5
6
查询A银行-2秒
查询B银行-3秒
查询C银行-4秒
查询D银行-5秒
展示结果集:[{"bank":"A","money":100,"cardNo":"1111"},{"bank":"B","money":100,"cardNo":"2222"},{"bank":"C","money":100,"cardNo":"3333"},{"bank":"D","money":100,"cardNo":"4444"}]
耗时 5059

OK,上面就是一个简单的Completablefuture使用Demo实践。下一篇再出一个Completablefuture使用原理
关于Completablefuture的文章,建议大家去看下 美团的技术博客