Future<V>接口 和 CompletableFuture<T>类 介绍
Future接口与CompletableFuture类
- Future<V>接口
- Future 接口代码
- Future使用场景
- Future使用代码示例
- Future鸡肋说明(为何引入了CompletableFuture)
- FutureTask<V>类
- CompletableFuture<T>类
- CompletableFuture类代码
- CompletableFuture使用示例
- CompletableFuture的创建方式
- CompletableFuture回调函数
- CompletableFuture链式调用
- CompletableFuture非阻塞获取结果(支持回调函数)
- CompletableFuture异常处理
- CompletableFuture组合任务
- 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使用代码示例
@Testpublic 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>() {@Overridepublic 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.类带有的静态工厂方法
@Testpublic 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类型的方法Apublic String A(){return "a";}//②再定义一个具有String类型参数的方法Bpublic 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()
任务完成后执行回调,返回新的结果
@Testpublic 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()
任务完成后执行回调,消费结果
@Testpublic void test() throws ExecutionException, InterruptedException {CompletableFuture<Void> c = CompletableFuture.supplyAsync(()-> "hello").thenAccept(System.out::println);}
输出内容hello
3.thenRun()
任务完成后执行回调,不消费结果
@Testpublic 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.获取结果基本用法
@Testpublic 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 {// 创建一个未完成的 CompletableFutureCompletableFuture<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) {// 创建一个未完成的 CompletableFutureCompletableFuture<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方法
@Testpublic 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对象
@Testpublic 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()会在两个任务都执行完成后,把两个任务的结果合并
@Testpublic 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"));}
}