Future,Callable,CompletableFuture是什么?
最近在复习rpc框架以及juc的时候,遇到了这三位长得相似的类,那么这一次,就来整理一下这三个究竟是什么、为什么出现、怎么用?
Future
是什么
作用:表示异步任务的结果
是否有返回值:有,通过future.get()获取
是否可以new:不可以,通过线程池返回
为什么出现
想象一下,在java多线程下我启动了一个线程/任务,那它执行完之后的结果我怎么拿到?
答:只能用共享变量 + 等待机制,非常麻烦
Future解决了什么问题?(注意是在java多线程下我启动了一个任务后)
任务是否执行完成? 用 isDone() 判断
任务的结果是什么? 用 get() 拿返回值
任务执行是否失败? get() 会抛出异常
想取消任务怎么办? 用 cancel() 方法
怎么用
先举一段使用Future的例子:
你去餐厅点了一个菜(提交任务),然后你去玩手机(做别的事情),等菜好了你再吃(调用 get())。
Future<Integer> future = executorService.submit(() -> {// 点菜return 42;
});
//区间你可以做别的事情
// 吃菜
Integer result = future.get();
这个就是Future最简单的用法,通过将这个点菜任务封装成为一个future,然后丢给线程池,在任务结束后获取任务的结果
再通俗点讲,Future就相当于外卖票据,你可以根据外卖票据来查询外卖完成了吗(future.isDone()),放哪去了(future.get())
Callable
是什么
作用:表示一个可以被线程异步执行,并且有返回值的任务
是否有返回值:有,通过 call() 方法返回
是否可以new:可以自己 new 一个 Callable,也可以用 lambda 表达式写
为什么出现
Runnable 是最早的任务接口,但它有两个缺陷:
- 无法返回执行结果
- 无法抛出受检异常
而 Callable 接口正好弥补了这两个问题:
- 它可以有返回值(通过 call 方法)
- 它可以抛出异常(call 方法支持 throws)
所以当我们希望线程有返回结果或者需要处理异常时,就会使用 Callable,而不是 Runnable
怎么用
先举一段使用 Callable 的例子:
你去餐厅点菜(写好菜单),厨房接单开始做(线程池异步执行),最后服务员把菜端上来(获取返回值)
Callable<Integer> task = () -> {// 做菜return 42;
};
Future<Integer> future = executorService.submit(task);
// 你可以做别的事情
Integer result = future.get(); // 吃菜
这个就是 Callable 最基本的用法,它相当于把一个“带返回值的任务”交给线程池执行,配合 Future 一起使用,就可以实现异步任务的结果处理。
再通俗点讲,Callable 就像是你填写的点菜单,交给服务员去下单,最终 Future 就会根据这个菜单给你送来结果。
在rpc中的应用:
首先我们定义一个函数,参数是Callable callable
/*** 重试** @param callable* @return* @throws Exception*/@Overridepublic RpcResponse doRetry(Callable<RpcResponse> callable) throws Exception {//一些重试策略…………//重试策略return callable.call();}
接着,我们可以将一个任务如发送请求封装进我们的Callable中去
rpcResponse = retryStrategy.doRetry(() ->VertxTcpClient.doRequest(rpcRequest, selectedServiceMetaInfo));
doRetry方法在执行完充实策略后,通过调用callable.call()便可以调用lambda表达式中的函数。
那么这就引出了Callable的另一个用法:将一段逻辑封装成“可延迟执行”的代码块,传递给其他方法,由这个方法在合适的时机调用它。
CompletableFuture
是什么
作用:表示更强大、可组合的异步任务结果,是 Future
的升级版
是否有返回值:有,通过 get()
或 join()
获取
是否可以new:可以,通过 CompletableFuture.supplyAsync()
等方式创建
为什么出现
Future
虽然可以获取异步结果,但有几个明显问题:
get()
是阻塞的,不能回调- 不能链式调用,比如一个任务完成后接着做另一个任务
- 多个任务之间组合困难(比如等待多个任务都完成再继续)
CompletableFuture
就是为了解决这些问题而出现的,具备以下能力:
任务是否执行完成? 用 isDone()
判断
任务完成后自动触发下一步? 用 thenApply()
、thenAccept()
等
多个任务组合执行? 用 thenCombine()
、allOf()
、anyOf()
等
是否支持异常处理? 支持,用 exceptionally()
、handle()
是否非阻塞? 支持回调、异步处理,不一定要阻塞调用
怎么用
先举一段使用 CompletableFuture
的例子:
你点了个菜(提交任务),并告诉服务员:菜做好了告诉我(注册回调),你继续刷手机(不阻塞),服务员叫你你再吃菜(消费结果)
CompletableFuture.supplyAsync(() -> {// 点菜return 42;
}).thenAccept(result -> {// 吃菜System.out.println("吃饭结果:" + result);
});
这个就是 CompletableFuture
最简单的用法。它不仅封装了一个异步任务,还支持任务完成后的处理逻辑。
再通俗点讲,CompletableFuture
就像你点外卖时不仅拿到了票据,还附加了功能:
1.外卖送达后自动通知你
2.送多个外卖一起吃
3.出问题时自动换一家重送
外卖送达后自动通知你
你点了一个外卖,外卖准备好后,服务员通过回调通知你去取餐,不需要你手动去查询。
CompletableFuture.supplyAsync(() -> {// 模拟点菜操作return "外卖准备好了";
}).thenAccept(result -> {// 当外卖准备好时自动通知System.out.println("外卖送达: " + result);
});
这里 thenAccept() 就是“外卖准备好后的自动通知”。当异步任务完成后,thenAccept() 会自动执行,通知你去取餐。
送多个外卖一起吃
// 模拟点外卖1
CompletableFuture<String> order1 = CompletableFuture.supplyAsync(() -> {return "外卖1准备好了";
});// 模拟点外卖2
CompletableFuture<String> order2 = CompletableFuture.supplyAsync(() -> {return "外卖2准备好了";
});// 模拟点外卖3
CompletableFuture<String> order3 = CompletableFuture.supplyAsync(() -> {return "外卖3准备好了";
});// 使用 allOf 等待所有外卖都准备好
CompletableFuture<Void> allOrders = CompletableFuture.allOf(order1, order2, order3);// 当所有外卖都准备好后,统一执行后续逻辑
allOrders.thenRun(() -> {try {// 获取所有外卖的结果并打印System.out.println(order1.get());Sy
CompletableFuture.allOf() 会等待 order1、order2 和 order3 三个任务都完成。
一旦所有任务都完成,thenRun() 会被触发,表示你可以去取所有的外卖。
在 thenRun() 中,我们使用 get() 方法获取每个外卖任务的结果,然后打印出来。
出问题时自动换一家重送
如果外卖店发生了问题(比如请求失败),我们可以自动选择另一家餐厅重送。
CompletableFuture<String> order = CompletableFuture.supplyAsync(() -> {// 模拟请求外卖失败throw new RuntimeException("外卖店出现问题");
});order.exceptionally(ex -> {// 发生异常时自动选择另一家餐厅System.out.println("发生异常,换家店: " + ex.getMessage());return "已换一家餐厅";
}).thenAccept(result -> {// 输出最终结果System.out.println("最终外卖结果: " + result);
});
exceptionally() 用来处理异常。如果 order 任务执行时抛出异常,exceptionally() 会捕获到并返回一个默认值(“已换一家餐厅”)。这相当于处理了异常并做了错误补偿,保证程序不会崩溃,最终的外卖结果会通过 thenAccept() 输出。
这下对这三个的理解是不是更深刻了一点,以前老是混淆,也分不清谁是谁,通过外卖的例子,理解了常见的函数操作,以后不要再忘记啦