深入理解Java并发:Future与CompletableFuture详解

知识背景:

在工作过程中有用到CompletableFuture,之前接触不多,特此下来学习一下,与大家一起分享!

总体介绍:

在多线程编程中,异步计算是一种常见的需求。其中Future和CompletableFuture是处理异步计算结果的两大核心接口。本文将详细介绍Future和CompletableFuture的概念、使用方法以及它们之间的区别。

一、Future

于Java5引入,一定程度上让一个线程池内的任务异步执行 通过它们可以在任务执行完毕之后得到任务执行结果。

Future接口可以构建异步应用,是多线程开发中常见的设计模式。

1.1 基本用法

java 复制代码
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(() -> {
    Thread.sleep(1000); // 模拟耗时操作
    return "Hello, Future!";
});

try {
    System.out.println("Doing something else...");
    String result = future.get(); // 阻塞等待结果
    System.out.println(result);
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}

1.2 主要方法

方法包括:

  • boolean isDone(): 判断任务是否已经完成。
  • T get(): 获取异步计算的结果。如果计算未完成,该方法会阻塞直到结果可用。
  • T get(long timeout, TimeUnit unit): 同上,但可以设置超时时间。

1.3 应用场景

当我们需要调用一个函数方法时。如果这个函数执行很慢,那么我们就要进行等待。但有时候,我们可能并不急着要结果。因此,我们可以让被调用者立即返回,让他在后台慢慢处理这个请求。对于调用者来说,则可以先处理一些其他任务,在真正需要数据的场合再去尝试获取需要的数据。

1.4 原理图示

其实就是并行的去做一些事情,这样我们就可以利用多核cpu的优势,来减少系统的消化的时间。

二、CompletableFuture:异步编程的进化

在Java8引入,实现了Future和CompletionStage接口,保留了Future的优点,并且弥补了其不足。即异步的任务完成后,需要用其结果继续操作时,无需等待。可以直接通过thenAccept、thenApply、thenCompose等方式将前面异步处理的结果交给另外一个异步事件处理线程来处理。

1.1 主要方法

  1. join() 方法,它的功能和 get() 方法是一样的,都是阻塞获取值,它们的区别在于 join() 抛出的是 unchecked Exception。这使得它可以在Stream.map()方法中用作方法引用

  2. runAsync 方法 不支持返回值

  3. supplyAsync 方法 可以支持返回值

示例代码:

java 复制代码
//无返回值
public static void runAsync() throws Exception {
    CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
        }
        System.out.println("run end ...");
    });
    
    future.get();
}

//有返回值
public static void supplyAsync() throws Exception {         
    CompletableFuture<Long> future = CompletableFuture.supplyAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
        }
        System.out.println("run end ...");
        return System.currentTimeMillis();
    });

    long time = future.get();
    System.out.println("time = "+time);
}

1.2 计算结果完成时的回调方法

当CompletableFuture的计算结果完成,或者抛出异常的时候,可以执行特定的Action。主要是下面的方法:

java 复制代码
public CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action)

可以看到Action的类型是BiConsumer<? super T,? super Throwable>它可以处理正常的计算结果,或者异常情况。

whenComplete 和 whenCompleteAsync 的区别:

  • whenComplete:是当某个任务执行完成后执行的回调方法,会将执行结果或者执行期间抛出的异常传递给回调方法,如果是正常执行则异常为null,回调方法对应的CompletableFuture的result和该任务一致,如果该任务正常执行,则get方法返回执行结果,如果是执行异常,则get方法抛出异常。
  • whenCompleteAsync:可能会另起一个线程执行任务,并且thenRunAsync可以自定义线程池,默认的使用ForkJoinPool.commonPool()线程池。

示例:

java 复制代码
public static void main(String[] args) throws ExecutionException, InterruptedException {
    CompletableFuture<Integer> cf1 = CompletableFuture.supplyAsync(() -> {
        System.out.println(Thread.currentThread() + " cf1 do something....");
        int a = 1/0;
        return 1;
    });

    CompletableFuture<Integer> cf2 = cf1.whenComplete((result, e) -> {
        System.out.println("上个任务结果:" + result);
        System.out.println("上个任务抛出异常:" + e);
        System.out.println(Thread.currentThread() + " cf2 do something....");
    });

    //        //等待任务1执行完成
    //        System.out.println("cf1结果->" + cf1.get());
    //        //等待任务2执行完成
    System.out.println("cf2结果->" + cf2.get());
}

1.3 thenCombine 合并任务

thenCombine 会把 两个 CompletionStage 的任务都执行完成后,把两个任务的结果一块交给 thenCombine 来处理。

java 复制代码
package com.chenYi.test.jdk;
 
import java.util.Random;
import java.util.concurrent.*;
 
public class Test {
    public static void main(String[] args) throws Exception {
 
        //任务1:洗水壶->烧开水
        CompletableFuture<Void> f1 = CompletableFuture.runAsync(() -> {
            try {
                System.out.println("T1:洗水壶...");
                Thread.sleep(1000);
                System.out.println("T1:烧开水...");
                Thread.sleep(15000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        //任务2:洗茶壶->洗茶杯->拿茶叶
        CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> {
            try {
                System.out.println("T2:洗茶壶...");
                Thread.sleep(1000);
                System.out.println("T2:洗茶杯...");
                Thread.sleep(2000);
                System.out.println("T2:拿茶叶...");
                Thread.sleep(1000);
 
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "龙井";
        });
        //任务3:任务1和任务2完成后执行:泡茶
        CompletableFuture<String> f3 = f1.thenCombine(f2, (__, tf) -> {
            System.out.println("T1:拿到茶叶:" + tf);
            System.out.println("T1:泡茶...");
            return "上茶:" + tf;
        });
        //等待任务3执行结果
        System.out.println(f3.join());
 
    }
}
 

1.4 原理图示

知识总结

Future为Java引入了异步计算的概念,而CompletableFuture在此基础上进行了全面升级,提供了更为强大和灵活的异步编程工具集。在进行高并发、高性能应用开发时,合理运用CompletableFuture能够显著提升系统的响应速度和资源利用率。然而,需要注意的是,过度复杂的调用可能会导致代码难以理解和维护,因此在设计时还需权衡可观性与效率。总之,掌握这两者是现代Java开发者必备的技能之一,它们将是你构建高效异步应用的强大武器!

相关推荐
许野平14 分钟前
Rust: 利用 chrono 库实现日期和字符串互相转换
开发语言·后端·rust·字符串·转换·日期·chrono
也无晴也无风雨17 分钟前
在JS中, 0 == [0] 吗
开发语言·javascript
狂奔solar25 分钟前
yelp数据集上识别潜在的热门商家
开发语言·python
duration~29 分钟前
Maven随笔
java·maven
zmgst32 分钟前
canal1.1.7使用canal-adapter进行mysql同步数据
java·数据库·mysql
跃ZHD41 分钟前
前后端分离,Jackson,Long精度丢失
java
blammmp1 小时前
Java:数据结构-枚举
java·开发语言·数据结构
何曾参静谧1 小时前
「C/C++」C/C++ 指针篇 之 指针运算
c语言·开发语言·c++
暗黑起源喵1 小时前
设计模式-工厂设计模式
java·开发语言·设计模式
WaaTong1 小时前
Java反射
java·开发语言·反射