概念
在Java8中,CompletableFuture提供 了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,并且提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合CompletableFuture的方法。
它可能代表一个明确完成的Future, 也有可能代表一个完成阶段( CompletionStage ),它支持在计算完成以后触发一些函数或执行某些动作。
它实现了Future和CompletionStaqe接口。
优点:
1. 异步任务结束时,会自动回调某个对象的方法;
2. 异步任务出错时,会自动回调某个对象的方法;
3. 主线程设置好回调后,不再关心异步任务的执行,异步任务之间可以顺序执行
runAsync与supplyAsync的区别
CompletableFuture.runAsync:无返回值,不使用自定义线程池时,使用默认线程池ForkJoinPool.commonPool
CompletableFuture.supplyAsync:有返回值,不使用自定义线程池时,使用默认线程池ForkJoinPool.commonPool
@Test
public void s1() throws ExecutionException, InterruptedException {
//创建线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 10, 1L,
TimeUnit.SECONDS, new LinkedBlockingQueue<>(50),
Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
//无返回值,使用默认线程池ForkJoinPool.commonPool
CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getName() + "----come in");
});
//无返回值,使用线程池
CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getName() + "----come in");
}, threadPoolExecutor);
System.out.println(future2.get()); //null
//有返回值
CompletableFuture<Integer> future3 = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "---come in");
return 10;
});
System.out.println(future3.get());//10
CompletableFuture<Integer> future4 = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "---come in");
return 11;
}, threadPoolExecutor);
System.out.println(future4.get());//11
//关闭线程池
threadPoolExecutor.shutdown();
}
}
CompletableFuture的API
1. 获得结果与触发计算
getNow,如果计算完结果,则用计算结果;否则用给定的值
complete,如果计算完结果则打断失败,获取计算结果;否则打断计算,使用给定的值
@Test
public void m1() throws ExecutionException, InterruptedException {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,10,1L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(50), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 1;
}, threadPoolExecutor);
//3秒后获取结果
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
// System.out.println(future.getNow(999));//如果计算完结果,则用计算结果;否则用给定的值
System.out.println(future.complete(-44)+"\t"+future.get());//如果计算完结果则打断失败,获取计算结果;否则打断计算,使用给定的值
threadPoolExecutor.shutdown();
}
以complete()为例
要求3秒后获取结果,计算只要2秒,所以直接使用计算结果
将需求改为1秒获取结果,计算仍要2秒,所有得不到计算结果,complete()返回true,并使用给定的-44
2. 对计算结果进行处理,有返回结果(针对同一线程)
thenApply,存在依赖关系,当前步错,不走下一步,当前步有异常就叫停
handle,当前步错,也可以走下一步,带着有异常的参数走
whenComplete(v,e)v:最终结果 e:异常
当无异常时,可以连续执行,将结果一步步赋给下一步,直到最后
@Test
public void m2() {
System.out.println(CompletableFuture.supplyAsync(() -> {
System.out.println("-------1");
return 1;
}).thenApply(f -> {
//int i = 10/0;
System.out.println("-------2"); //thenApply存在依赖关系,当前步错,不走下一步,当前步有异常就叫停
return f + 1;
}).thenApply(f -> {
System.out.println("-------3");
return f + 2;
}).whenComplete((v, e) -> {
if (e == null) {
System.out.println("----result: " + v);
}
}).exceptionally(e -> {
e.printStackTrace();
return null;
}).join());
}
当有异常时,thenApply只执行到出错的那一步
当有异常时,handle,也可以走下一步,带着有异常的参数走
System.out.println(CompletableFuture.supplyAsync(() -> {
System.out.println("-------1");
return 1;
}).handle((f,e) -> {
int i = 10/0;
System.out.println("-------2"); //handle,当前步错,也可以走下一步,带着有异常的参数走
return f + 1;
}).handle((f,e) -> {
System.out.println("-------3");
return f + 2;
}).whenComplete((v, e) -> {
if (e == null) {
System.out.println("----result: " + v);
}
}).exceptionally(e -> {
e.printStackTrace();
return null;
}).join());
3. 对计算结果消费,无返回结果
thenRun,无输入,无返回值
thenApply,有输入,有返回值
thenAccept,有输入,无返回值
@Test
public void m3() {
CompletableFuture.supplyAsync(()->{
return 1;
}).thenApply( f -> {
return f+1;
}).thenApply( f -> {
return f+2;
}).thenAccept(c -> {
System.out.println(c);
});
//无输入,无返回值
System.out.println(CompletableFuture.supplyAsync(()-> "resultA").thenRun(()->{}).join());
//有输入,有返回值
System.out.println(CompletableFuture.supplyAsync(()-> "resultB").thenApply(resultB->resultB+" resultC").join());
//有输入,无返回值
System.out.println(CompletableFuture.supplyAsync(()-> "resultD").thenAccept(resultD ->{}).join());
}
4. 对计算速度进行选用
applyToEither,比较计算速度,选择速度较快的作为返回结果
1号玩家完成任务需要1秒,2号玩家完成任务需要2秒,故判断1号玩家赢。(两个玩家线程都是需要执行的,再判断速度取结果)
@Test
public void m4() throws InterruptedException {
System.out.println(CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1号玩家");
return 1;
}).applyToEither(CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("2号玩家");
return 2;
}), r -> {
System.out.println(r+"号玩家赢");
return r;
}).join());
TimeUnit.SECONDS.sleep(3);
}
5. 对计算结果进行合并
thenCombine,合并两个线程的结果
先计算0-10的和,再计算10-20的和,两者合并相加,在与40进行合并相加
@Test
public void m5() throws InterruptedException {
System.out.println(CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
int s1 = 0;
for (int i = 0; i < 10; i++) {
s1 += i;
}
return s1;
}).thenCombine(CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
int s2 = 0;
for (int i = 10; i < 20; i++) {
s2 += i;
}
return s2;
}), (r1, r2) -> {
return r1 + r2;
}).thenCombine(CompletableFuture.supplyAsync(()->{
return 40;
}),(r1,r2)->{
return r1+r2;
}).join());//还可继续合并
TimeUnit.SECONDS.sleep(3);
}
6. 对计算结果进行处理,有返回结果(针对不同线程)
thenCompose,与thenApply类似,有输入,有返回值,但是输入是针对不同的线程之间
@Test
public void m6() {
System.out.println(CompletableFuture.supplyAsync(() -> {
return 1;
}).thenCompose(f -> CompletableFuture.supplyAsync(() -> {
return f + 1;
})).join());
}
CompletableFuture的实例
需求:同一款产品,同时搜索出同款产品在各大电商的售价;
结果:希望是同款产品的在不同地方的价格清单列表,返回- - list
比较:将同步实现的方法与异常实现的方法进行对比
public class CompletableFutureNetMallDemo {
//模拟不同平台
static List<NetMall> list = Arrays.asList(
new NetMall("jd"),
new NetMall("pdd"),
new NetMall("pdd"),
new NetMall("pdd"),
new NetMall("pdd"),
new NetMall("pdd"),
new NetMall("pdd"),
new NetMall("pdd"),
new NetMall("tmall")
);
//同步完成 step by step
public static List<String> getPriceByStep(List<NetMall> list, String bookName) {
return list.stream().map(netMall -> String.format
(bookName + "in %s price is %.2f", netMall.getMallName(), netMall.getPrice(bookName)))
.collect(Collectors.toList());
}
//异步完成 多箭齐发
public static List<String> getPriceByASync(List<NetMall> list,String bookName){
return list.stream().map(netMall -> CompletableFuture.supplyAsync(()-> String.format
(bookName + "in %s price is %.2f", netMall.getMallName(), netMall.getPrice(bookName))))
.collect(Collectors.toList()) //List<CompletableFuture<String>>
.stream().map(CompletableFuture::join).collect(Collectors.toList());//List<String>
}
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
List<String> list1 = getPriceByStep(list, "mysql");
for (String element : list1){
System.out.println(element);
}
long endTime = System.currentTimeMillis();
System.out.println("----byStep:"+(endTime-startTime)+"毫秒");
long startTime2 = System.currentTimeMillis();
List<String> list2 = getPriceByASync(list, "mysql");
for (String element : list2){
System.out.println(element);
}
long endTime2 = System.currentTimeMillis();
System.out.println("----byASync:"+(endTime2-startTime2)+"毫秒");
}
}
class NetMall {
@Getter
private String mallName;
public NetMall(String mallName) {
this.mallName = mallName;
}
//模拟获取价格
public Double getPrice(String bookName) {
return ThreadLocalRandom.current().nextDouble() * 2 + bookName.charAt(0);
}
}
比较结果可发现,同步方法step by step消耗时间远超异步方法。(数据规模越大,越明显)