Future<V>接口 和 CompletableFuture<T>类 介绍


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"));
    }
}