package com.example.demo;
import lombok.SneakyThrows;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
/**
* concurrent并发包CompletableFuture API
*
* @author majun
* @version 1.0
* @since 2023-08-12 21:05
*/
public class Test {
public static final int MAX_SCORE = 100;
public static final int INIT_SCORE = 0;
/**
* 场景:固定人数,且人数较少的考试,输出最高分
*
* @throws ExecutionException
* @throws InterruptedException
*/
public static void threeStudentsKaoShi(){
Integer res = CompletableFuture.completedFuture(INIT_SCORE).thenApplyAsync(score -> kaoShi(1, "语文")).thenApply(score -> kaoShi(1, "数学"))
.thenCombine(CompletableFuture.completedFuture(INIT_SCORE).thenApplyAsync(score -> kaoShi(2, "语文")).thenApply(score -> kaoShi(2, "数学")), Integer::max)
.thenCombine(CompletableFuture.completedFuture(INIT_SCORE).thenApplyAsync(score -> kaoShi(3, "语文")).thenApply(score -> kaoShi(3, "数学")), Integer::max)
.exceptionally(throwable -> {
System.out.println(throwable.getMessage());
return 0;
}).join(); // 类似同thread#join,main线程会等待子线程完成,这里也可以调用get阻塞方法来获取最终结果
System.out.println(res);
}
/**
* 场景:随机人数,输出最高分
*
* @throws ExecutionException
* @throws InterruptedException
*/
static void randomNumStudentsKaoShi() throws ExecutionException, InterruptedException {
// 随机n各学生考试
CompletableFuture[] array = Stream.iterate(0, n -> n + 1)
.limit(getRandomInt())
.map(id -> CompletableFuture.completedFuture(INIT_SCORE)
.thenApplyAsync(score -> kaoShi(id, "语文")).thenApply(score -> kaoShi(id, "数学")))
.toArray(CompletableFuture[]::new);
System.out.println("考试人数:" + array.length);
// 考试结果合计最高分:whenComplete 上一步CompletableFuture.allOf返回的是泛型是Void类型,unused始终是null,异步运行结果只能从CompletableFuture[]遍历获取
CompletableFuture<Void> future = CompletableFuture.allOf(array).whenComplete((unused, throwable) -> {
if (throwable == null) {
Integer maxScore = Arrays.stream(array).map(item -> {
try {
return (int) item.get();
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}).max(Integer::compareTo).get();
System.out.println(maxScore);
}
});
future.get();
}
/**
* 考试
*
* @param studentId
* @param subject 科目
* @return 得分
*/
@SneakyThrows
private static int kaoShi(int studentId, String subject) {
System.out.println(studentId + "开始考试" + subject);
int timeCost = getRandomInt();
TimeUnit.SECONDS.sleep((int) (timeCost));
System.out.println(studentId + subject + "考试耗时" + timeCost);
return MAX_SCORE - getRandomInt();
}
/**
* 1-10随机数
*
* @return
*/
private static int getRandomInt() {
return (int) (Math.random() * 10 + 1);
}
@SneakyThrows
public static void main(String[] args) {
System.out.println(LocalDateTime.now());
//threeStudentsKaoShi();
randomNumStudentsKaoShi();
System.out.println(LocalDateTime.now());
}
}
总结:
- 该API适合多线程多阶段业务场景,可以很方便的设定下一步任务、同步/异步,使用CountdownLaunch/CircleBarrier+多线程也可以实现
- thenApplyAsync API默认是基于Main线程拉起子线程异步执行,也可以传入线程池
- spring ListenableFuture是基于concurrent.CompletableFuture实现的,但该API的回调方法即终结了,不便添加后续Stage。