Future接口与CompletableFuture类
Future接口
在API文档中描述大意是,用于表示异步计算的结果。它提供了一种机制,允许我们在任务提交后获取任务的执行状态和结果。Future 通常与 ExecutorService线程池 结合使用,用于管理异步任务的执行
这个我们先理解同步异步的区分
- 同步:发送一个请求,等待返回,然后再发送下一个请求
- 异步:发送一个请求,不等待返回,随时可以再发送下一个请求
同步和异步最大的区别就在于。一个需要等待,一个不需要等待
- 同步例子:电话,发起者需要等待接收者,接通电话后,通信才开始。需要等待接收者的返回信息
- 异步例子:广播,发起者不关心接收者的状态。不需要等待接收者的返回信息
Future 接口代码
public interface Future<V> {
// 尝试取消任务。如果任务正在运行,mayInterruptIfRunning 决定是否中断任务
boolean cancel(boolean mayInterruptIfRunning);
//检查任务是否被取消
boolean isCancelled();
//检查任务是否完成(正常完成、取消或异常)
boolean isDone();
//获取任务结果(阻塞直到任务完成)
V get() throws InterruptedException, ExecutionException;
//获取任务结果(最多等待指定时间)
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
Future使用场景
1.异步任务执行:提交任务后,继续执行其他逻辑,稍后获取任务结果
2.任务状态监控:检查任务是否完成、是否被取消。
3.任务取消:在任务完成前取消任务
Future使用代码示例
@Test
public void Test() throws InterruptedException, ExecutionException {
// 创建线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
// 提交 Callable 任务,返回 Future 对象
Future<Integer> future = executorService.submit(()->{
Thread.sleep(100);
return 500;
});
// 检查任务是否完成
System.out.println("Task is done: " + future.isDone());
// 获取任务结果(阻塞直到任务完成)
Integer result = future.get();
System.out.println("Task result: " + result);
// 再次检查任务状态
System.out.println("Task is done: " + future.isDone());
// 关闭线程池
executorService.shutdown();
}
Future鸡肋说明(为何引入了CompletableFuture)
虽然Future官方定义是异步的机制,但Future并不能实现真正的异步
1.如Future.get()方法获取的是任务状态和结果,但需要阻塞获取结果,很好理解要结果需要等到任务结束才有结果,它要等任务完成了才会去执行get方法,get方法从等待到执行就是一个阻塞过程
阻塞定义是:指的是线程能够运行,但是某个条件阻止它的运行,当线程处于阻塞状态时,调度器将忽略线程,不会分配给线程任何CPU时间,直到线程重新进入就绪状态,它才有可能执行操作
2.又或者boolean isDone();boolean isCancelled();这两个方法需要我们自定义执行去查询从而获取到任务的状态,这样也不算异步
所以有了更强大的 CompletableFuture类实现真正的异步,以下开始介绍 CompletableFuture类 以及怎么实现异步的
FutureTask类
FutureTask类 算是Future接口的一个实现类,通过实现RunnableFuture接口,而RunnableFuture接口又继承了Runnable和Future接口,所以它拥有实现Runnable, Future两个接口的功能,及多线程实现和异步计算结果获取功能,算个杂交体吧
public class FutureTask<V> implements RunnableFuture<V> {
}
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
它的方法和Future接口的方法大体一致,获取Callable 任务返回结果或异常区别在于它又Runnale接口的功能,即它本身可以作为一个线程任务可以用start方式启动执行FutureTask,也可以想Future一样交给线程池启动执行
FutureTask<Integer> futureTask = new FutureTask<>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
// 异步任务逻辑
return 42;
}
});
// 方法一:直接启动线程
new Thread(futureTask).start();
// 方法二:使用ExecutorService
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.submit(futureTask);
try {
Integer result = futureTask.get(); // 阻塞直到任务完成
System.out.println("Result: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
CompletableFuture类
它是对 Future 的增强类,实现Future 接口并新增许多方法,提供了更强大的功能,如 非阻塞获取结果、回调函数、链式调用、异常处理 、组合任务、手动完成任务等
CompletableFuture类代码
public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
//获取结果
get();//阻塞获取结果。
join();//阻塞获取结果,但不抛出受检异常。
getNow(T valueIfAbsent);//立即获取结果,如果任务未完成,返回默认值。
//回调函数
thenApply();//任务完成后执行回调,返回新的结果。
thenAccept();//任务完成后执行回调,消费结果。
thenRun();//任务完成后执行回调,不消费结果。
//异常处理
exceptionally();//处理任务抛出的异常。
handle();//无论任务是否成功,都会执行回调。
//组合任务
thenCompose();//将两个任务串联执行。
thenCombine();//将两个任务并联执行,并合并结果。
allOf();//等待所有任务完成。
anyOf();//等待任意一个任务完成
//手动任务
complete("Manual result"); // 手动设置结果
}
CompletableFuture使用示例
CompletableFuture的创建方式
1.构造器方式 2.类带有的静态工厂方法
@Test
public void test(){
//创建CompletableFuture构造方法创建CompletableFuture实例对象
CompletableFuture<String> completableFuture = new CompletableFuture<String>();
completableFuture.complete("构造方法创建实例 手动设置结果");
// 以下三种 使用CompletableFuture类带有的静态工厂方法
CompletableFuture<String> future =
CompletableFuture.completedFuture("通过类自带静态方法创建一个");
CompletableFuture<Void> future1 = completableFuture.runAsync(()->{
System.out.println("创建一个异步任务但不需要返回结果");
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(()->{
return "创建一个异步任务并返回结果";
});
}
CompletableFuture回调函数
这里先引入回调函数概念与示例
回调函数(Callback Function):是指使用者自己定义一个函数,然后把这个函数作为参数传入别的函数中,由别的函数在运行时来调用的函数。函数是你实现的,但由别的函数在运行时通过参数传递的方式调用,这就是所谓的回调函数。
简单来说,就是由别人的函数运行期间来回调你实现的函数
示例
//①定义一个具有返回值String类型的方法A
public String A(){
return "a";
}
//②再定义一个具有String类型参数的方法B
public void B(String str){
}
public static void main(String[] args) {
SystemTest st = new SystemTest();
//③将A方法的返回值作为参数放在方法B中,这样调用方法B时,会调用方法A 这个过程就是回调
st.B(st.A());
}
再看下 CompletableFuture涉及到的回调函数具体使用方式
1.thenApply()
任务完成后执行回调,返回新的结果
@Test
public void test() throws ExecutionException, InterruptedException {
CompletableFuture<String> c = CompletableFuture.supplyAsync(()-> "hello")
.thenApply(resultData->resultData+"world");
System.out.println(c.get());
}
①首先创建了一个CompletableFuture对象,并使用supplyAsync方法异步执行一个任务,该任务返回字符串"hello"
②我们使用thenApply方法对这个结果回调,进行处理,将其与字符串"world"拼接起来。
③我们通过get方法获取最终的结果并打印出来
2.thenAccept()
任务完成后执行回调,消费结果
@Test
public void test() throws ExecutionException, InterruptedException {
CompletableFuture<Void> c = CompletableFuture.supplyAsync(()-> "hello")
.thenAccept(System.out::println);
}
输出内容hello
3.thenRun()
任务完成后执行回调,不消费结果
@Test
public void test() throws ExecutionException, InterruptedException {
CompletableFuture<Void> c = CompletableFuture.supplyAsync(()-> "hello")
.thenRun(System.out::println);
}
打印台为空,没有打印内容
CompletableFuture链式调用
链式调用的概念:通过在方法调用后返回对象本身,实现对方法的连续调用
public class Calculator {
private int result;
public Calculator() {
this.result = 0;
}
public Calculator add(int num) {
this.result += num;
return this;
}
public Calculator subtract(int num) {
this.result -= num;
return this;
}
public Calculator multiply(int num) {
this.result *= num;
return this;
}
public Calculator divide(int num) {
if (num != 0) {
this.result /= num;
}
return this;
}
public int getResult() {
return this.result;
}
public static void main(String[] args) {
Calculator calculator = new Calculator();
int result = calculator.add(5).subtract(3).multiply(4).divide(2).getResult();
System.out.println(result); // 输出:8
}
}
Calculator类实现了四个基本的数学运算方法:add、subtract、multiply和divide。每个方法都返回this,即当前对象,以实现链式调用。通过这种方式,可以在一个表达式中连接多个方法,使代码更加流畅和易于理解
CompletableFuture可以使用多个方法串联使用实现链式调用
public class CompletableFutureChainExample {
public static void main(String[] args) {
CompletableFuture.supplyAsync(() -> "Hello")
.thenApply(s -> s + ", World") // 串联任务
.thenAccept(System.out::println); // 消费结果
}
}
CompletableFuture非阻塞获取结果(支持回调函数)
1.获取结果基本用法
@Test
public void test() throws ExecutionException, InterruptedException {
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(()->{
return "创建一个异步任务并返回结果";
});
System.out.println(future2.get());
}
这个示例上get方法想要执行获取结果 需要task任务完成就是future2完成supplyAsync方法返回结果示例,get方法从等待执行到执行过程就是阻塞过程
2.非阻塞获取结果
那CompletableFuture是如何非阻塞获取结果的的呢,这就涉及到上面写的CompletableFuture有回调函数和链式调用的设计了
CompletableFuture回调函数有thenApply ,thenAccpt,Handle等,已thenApply为例说明
thenApply 是 CompletableFuture 中的一个方法,用于在前一个 CompletableFuture 完成之后应用一个函数,并返回一个新的 CompletableFuture。它的核心特点是 异步链式调用,即前一个任务完成后,自动触发后续任务
1.前一个任务完成后
thenApply 会自动触发,并将前一个任务的结果作为输入,应用指定的函数,返回一个新的 CompletableFuture
public class ThenApplyExample {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建一个未完成的 CompletableFuture
CompletableFuture<String> future = new CompletableFuture<>();
// 注册 thenApply 回调
CompletableFuture<String> transformedFuture = future.thenApply(result -> {
System.out.println("Applying transformation...");
return result.toUpperCase(); // 将结果转换为大写
});
// 手动完成前一个任务
future.complete("hello");
// 获取转换后的结果
String result = transformedFuture.get();
System.out.println("Transformed result: " + result);
}
}
输出内容
Applying transformation...
Transformed result: HELLO
2.前一个任务未完成
thenApply 不会立即执行,而是将函数注册为回调,等待前一个任务完成后再执行
public class ThenApplyPendingExample {
public static void main(String[] args) {
// 创建一个未完成的 CompletableFuture
CompletableFuture<String> future = new CompletableFuture<>();
// 注册 thenApply 回调
CompletableFuture<String> transformedFuture = future.thenApply(result -> {
System.out.println("Applying transformation...");
return result.toUpperCase(); // 将结果转换为大写
});
// 不手动完成任务
System.out.println("Main thread continues...");
}
}
输出内容
Main thread continues...
thenApply会根据前一个任务是否完成而决定是否触发,本质上就是前一个完成作为参数传入该方法然后执行,如果前一个未完成就意味着没有传入参数就不会触发执行
总结:
1.thenApply是非阻塞方法 ,调用 thenApply 时会立即返回一个新的 CompletableFuture,而不会阻塞当前线程,获取过程没有阻塞,我们不用等任务结束去get,自动触发执行,这就是非阻塞获取结果,但是如果前一个未完成,等待前一个完成再去触发 thenApply方法执行,这个过程是阻塞的,但这是thenApply方法执行之前阻塞和thenApply方法无关,也就是 获取结果是否阻塞 无关
2.thenApply通常与 supplyAsync() 等异步任务结合使用 , supplyAsync() 是在创建实例就返回值,速度很快的
3.支持链式调用,可以串联多个 thenApply
CompletableFuture异常处理
1.链式调用exceptionally方法
@Test
public void test(){
CompletableFuture.supplyAsync(()->{
throw new RuntimeException("Task failed");
}).exceptionally(ex->{
System.out.println("Exception: " + ex.getMessage());
return "fail";
}).thenAccept(System.out::println);
}
打印内容
Exception: java.lang.RuntimeException: Task failed
fail
1.链式调用handle方法
方法参数handle(BiFunction<? super T, Throwable, ? extends U> fn)
public void test2(){
CompletableFuture.supplyAsync(() -> {
// 模拟可能抛出异常的操作
throw new RuntimeException("Exception occurred");
}).handle((result, exception) -> {
//处理计算结果或者异常情况
if (exception != null) {
//计算中判断是否出现异常
// andle the exception 处理异常情况
System.out.println("Exception handled: " + exception.getMessage());
return "Default value"; // 返回默认值
} else {
// handle the result处理正常结果
return result;
}
}).thenAccept(System.out::println);
}
1.supplyAsync创建了一个CompletableFuture,并在其计算过程中抛出了一个异常。
2.handle方法接收一个BiFunction,用于处理计算结果或者异常情况。如果计算中出现异常,BiFunction的第二个参数(上面示例参数exception)会包含该异常信息,否则该参数为null。
3.在这个例子中,我们判断如果出现异常,会打印异常信息,并返回一个默认值。否则返回结果
CompletableFuture组合任务
thenCompose()方法 将两个任务串联执行
用于将一个CompletableFuture实例转化为另一个CompletableFuture实例。它接收一个Function参数,该参数将前一个CompletableFuture返回的结果作为输入,并返回一个新的CompletableFuture对象
@Test
public void test(){
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(()->"hello");
CompletableFuture<Void> future2 = future1.thenCompose(s -> {
// 将前一个任务的结果作为参数传递给下一个任务
return CompletableFuture.supplyAsync(() -> s + " World");
}).thenAccept(System.out::println);
}
thenCombine()方法 将两个任务并联执行,并合并结果
用于将两个CompletableFuture实例合并为一个。它接收另一个CompletableFuture实例和一个BiFunction参数(BiFunction是一个函数式接口,接受两个输入值并返回一个结果),BiFunction参数参数将两个CompletableFuture返回的结果作为输入,并返回一个新的CompletableFuture对象3。thenCombine()会在两个任务都执行完成后,把两个任务的结果合并
@Test
public void test(){
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(()->"hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(()->"World");
future1.thenCombine(future2, (s1,s2) -> s1+","+ s2).thenAccept(System.out::println);
}
总的来说,thenCompose()适用于对结果进行连续变换和处理的异步操作,而thenCombine()适用于将两个独立的异步操作的结果合并
CompletableFuture手动任务
allOf();//等待所有任务完成
public class CompletableFutureAllOfExample {
public static void main(String[] args) {
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Task 1");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Task 2");
CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> "Task 3");
// 等待所有任务完成
CompletableFuture.allOf(future1, future2, future3)
.thenRun(() -> System.out.println("All tasks completed"));
}
}