【JUC】Future + CompletableFuture详解

文章目录

  • [1. Future接口](#1. Future接口)
    • [1.1 Future 的理论知识](#1.1 Future 的理论知识)
    • [1.2 Future接口的三个特点](#1.2 Future接口的三个特点)
    • [1.3 关于Future存在的问题](#1.3 关于Future存在的问题)
    • [1.4 Future的架构](#1.4 Future的架构)
    • [1.5 Future编码实战和优缺点分析](#1.5 Future编码实战和优缺点分析)
    • [1.6 Future的使用举例](#1.6 Future的使用举例)
  • [2.Future 到 CompletableFuture 的过渡](#2.Future 到 CompletableFuture 的过渡)
  • 3.CompletableFuture接口
    • [3.1 CompletableFuture为什么会出现](#3.1 CompletableFuture为什么会出现)
    • [3.2 CompletableFuture架构](#3.2 CompletableFuture架构)
    • [3.3 CompletableFuture的类特点](#3.3 CompletableFuture的类特点)
    • [3.4 核心的四个静态方法,来创建一个异步任务](#3.4 核心的四个静态方法,来创建一个异步任务)
    • [3.5 CompletableFuture的四个静态方法的使用](#3.5 CompletableFuture的四个静态方法的使用)
    • [3.6 CompletableFuture的优点](#3.6 CompletableFuture的优点)
    • CompletableFuture常用方法

1. Future接口

1.1 Future 的理论知识

Future接口(实现类:FutureTask)定义了操作异步任务执行一些方法,如获取异步任务的执行结果、取消异步任务的执行、判断任务是否被取消、判断任务执行是否完毕等。

举例:比如主线程让一个子线程去执行任务,子线程可能比较耗时,启动子线程开始执行任务后,主线程就去做其他事情了,忙完其他事情或者先执行完,过了一会再才去获取子任务的执行结果或变更的任务状态(老师上课时间想喝水,他继续讲课不结束上课这个主线程,让学生去小卖部帮老师买水完成这个耗时和费力的任务)。

复制代码
public interface Future<V> {
//取消异步任务的执行
boolean cancel(boolean mayInterruptIfRunning);
//判断异步任务是否被取消
boolean isCancelled();
//判断任务是否被执行完毕
boolean isDone();
//获取到异步任务的结果
V get() throws InterruptedException, ExecutionException;
//只在timeout时间内等待获取异步任务的结果
V get(long timeout, TimeUnit unit)
       throws InterruptedException, ExecutionException, TimeoutException;
}

1.2 Future接口的三个特点

  1. 多线程
  2. 有返回值
  3. 异步任务

1.3 关于Future存在的问题

  • 现在 Thread (Runnable target,String name ) 可以满足特点1
  • 有返回值则要使用callable,但是初始化Thread不可以传参Callable,只能传参Runnable
  • 异步任务则需要实现future接口

1.4 Future的架构

这里要通过Future的架构来理解Future是如何解决这些问题的

  • FutureTask作为Future的实现类,继承了RunnableFuture接口,而RunnableFuture接口又继承了FutureRunnable接口;在FutureTask,即Future的落地实现类中,重写了run()方法,并在这个重写的run()方法中调用了callable方法,这样便可以解决返回值的问题
  • RunnableFuture 接口的定义可以看出,它继承了 Runnable 接口, 那么这样,就可以将 FutureTask 类以构造方法参数的形式传递给Thread了。
  • RunnableFuture 接口中有一个 run 方法,那么这就要求实现 RunnableFuture 接口的类要去实现了 run() 方法。这样,FutureTask 类既然实现了 RunnableFuture 接口,那么 FutureTask 类中必然有一个 run 方法是供 Thread 类调用的。

源码解读:

1.5 Future编码实战和优缺点分析

  • 优点:Future+线程池异步多线程任务配合,能显著提高程序的运行效率。
  • 缺点:
    • get()阻塞---一旦调用get()方法求结果,一旦调用不见不散,非要等到结果才会离开,不管你是否计算完成,如果没有计算完成容易程序堵塞。
    • isDone()轮询---轮询的方式会耗费cpu资源,而且也不见得能及时得到计算结果,如果想要异步获取结果,通常会以轮询的方式去获取结果,尽量不要阻塞。

● 结论: Future对于结果的获取不是很友好,只能通过阻塞或轮询的方式得到任务的结果

1.6 Future的使用举例

复制代码
public class FutureApiDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
        FutureTask<String> futureTask = new FutureTask<>(() -> {
            System.out.println(Thread.currentThread().getName() + "--------come in");
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "task over";
        });

        Thread t1 = new Thread(futureTask, "t1");
        t1.start();
        System.out.println(Thread.currentThread().getName() + " ------忙其他任务");
        while (true) {//轮询
            if(futureTask.isDone()){
                System.out.println(futureTask.get());
                break;
            }else{
                TimeUnit.MILLISECONDS.sleep(500);
                System.out.println("正在处理中,不要催了,越催越慢");
            }
        }
    }
}

2.Future 到 CompletableFuture 的过渡

● 对于简单的业务场景使用Future完全ok

回调通知:

○ 应对Future的完成时间,完成了可以告诉我,也就是我们的回调通知

○ 通过轮询 的方式去判断任务是否完成这样非常占cpu并且代码也不优雅

● 创建异步任务:Future + 线程池组合

● 多个任务前后依赖可以组合处理(水煮鱼--->买鱼--->调料--->下锅):

○ 想将多个异步任务的结果组合起来,后一个异步任务的计算结果需要钱一个异步任务的值

○ 想将两个或多个异步计算合并成为一个异步计算,这几个异步计算互相独立,同时后面这个又依赖前一个处理的结果

● 对计算速度选最快的:

○ 当Future集合中某个任务最快结束时,返回结果,返回第一名处理结果

● 结论:

○ 使用Future之前提供的API功能太少,处理起来不够优雅,这时候还是让CompletableFuture以声明式的方式优雅的处理这些需求。

3.CompletableFuture接口

3.1 CompletableFuture为什么会出现

get()方法在Future计算完成之前会一直处在阻塞状态下 ,阻塞的方式和异步编程的设计理念相违背。

isDone()方法容易耗费cpu资源(cpu空转),

● 对于真正的异步处理我们希望是可以通过传入回调函数,在Future结束时自动调用该回调函数 ,这样,我们就不用等待结果
jdk8设计出CompletableFutureCompletableFuture提供了一种观察者模式类似的机制,可以让任务执行完成后通知监听的一方

3.2 CompletableFuture架构

● 接口CompletionStage:

○ 代表异步计算 过程中的某一个阶段,一个阶段完成以后可能会触发另外一个阶段。

○ 一个阶段的执行可能是被单个阶段的完成触发,也可能是由多个阶段一起触发。

● 类CompletableFuture

○ 提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,并且提供了函数式编程的能力,可以通过回调的方式处理计算结果 ,也提供了转换和组合CompletableFuture的方法

○ 它可能代表一个明确完成的Future,也可能代表一个完成阶段(CompletionStage),它支持在计算完成以后触发一些函数或执行某些动作

3.3 CompletableFuture的类特点

  1. 分阶段链式执行方法,上一个阶段的结果可以传递给下一个阶段。
  2. 主线程不需要阻塞或轮询等待结果 ,而是交给CompletableFuture获取结果。整个过程正常完成和异常完成情况,CompletableFuture可以分别处理。
  3. 多个CompletableFuture可以取先执行的结果或者取合并后的结果。

3.4 核心的四个静态方法,来创建一个异步任务

对于上述Executor参数说明:若没有指定,则使用默认的ForkJoinPoolcommonPool()作为它的线程池执行异步代码,如果指定线程池,则使用我们自定义的或者特别指定的线程池执行异步代码。

3.5 CompletableFuture的四个静态方法的使用

  • runAsync 无返回值

    public static CompletableFuture<Void> runAsync(Runnable runnable)
    public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)

    public class CompletableFutureBuildDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    ExecutorService executorService = Executors.newFixedThreadPool(3);

    复制代码
          CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
              System.out.println(Thread.currentThread().getName());
              try {
                  TimeUnit.SECONDS.sleep(1);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          },executorService);
    
          System.out.println(completableFuture.get()); //null
    
    
          CompletableFuture<String> objectCompletableFuture = CompletableFuture.supplyAsync(()->{
              System.out.println(Thread.currentThread().getName());
              try {
                  TimeUnit.SECONDS.sleep(1);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              return "hello supplyAsync";
          },executorService);
    
          System.out.println(objectCompletableFuture.get());//hello supplyAsync
    
          executorService.shutdown();
    
      }

    }

运行结果

  • supplyAsync 有返回值

    public static CompletableFuture supplyAsync(Supplier supplier)
    public static CompletableFuture supplyAsync(Supplier supplier, Executor executor)

    public class CompletableFutureUseDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    ExecutorService executorService = Executors.newFixedThreadPool(3);
    CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
    System.out.println(Thread.currentThread().getName() + "---come in");
    int result = ThreadLocalRandom.current().nextInt(10);
    try {
    TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    if (result > 5) { //模拟产生异常情况
    int i = 10 / 0;
    }
    System.out.println("----------1秒钟后出结果" + result);
    return result;
    }, executorService).whenComplete((v, e) -> {
    if (e == null) {
    System.out.println("计算完成 更新系统" + v);
    }
    }).exceptionally(e -> {
    e.printStackTrace();
    System.out.println("异常情况:" + e.getCause() + " " + e.getMessage());
    return null;
    });
    System.out.println(Thread.currentThread().getName() + "先去完成其他任务");
    executorService.shutdown();
    }
    }

运行结果:

3.6 CompletableFuture的优点

CompletableFuture优点:

● 异步任务结束 时,会自动回调某个对象的方法

● 主线程设置好回调后 ,不用关心异步任务的执行,异步任务之间可以顺序执行

● 异步任务出错 时,会自动回调某个对象的方法

CompletableFuture常用方法

● 获得结果和触发计算

○ 获取结果

public T get()

public T get(long timeout,TimeUnit unit)

public T join()get 一样的作用,只是不需要抛出异常

public T getNow(T valuelfAbsent) :计算完成就返回正常值,否则返回备用值(传入的参数),立即获取结果不阻塞

○ 主动触发计算

public boolean complete(T value) :是否打断get方法立即返回括号值

● 对计算结果进行处理

thenApply:计算结果存在依赖关系,这两个线程串行化,由于存在依赖关系(当前步错,不走下一步),当前步骤有异常的话就叫停

handle: 计算结果存在依赖关系,这两个线程串行化,有异常也可以往下走一步

相关推荐
Coder码匠8 小时前
Dockerfile 优化实践:从 400MB 到 80MB
java·spring boot
李慕婉学姐15 小时前
【开题答辩过程】以《基于JAVA的校园即时配送系统的设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
java·开发语言·数据库
数据皮皮侠AI17 小时前
上市公司股票名称相似度(1990-2025)
大数据·人工智能·笔记·区块链·能源·1024程序员节
奋进的芋圆17 小时前
Java 延时任务实现方案详解(适用于 Spring Boot 3)
java·spring boot·redis·rabbitmq
sxlishaobin17 小时前
设计模式之桥接模式
java·设计模式·桥接模式
model200517 小时前
alibaba linux3 系统盘网站迁移数据盘
java·服务器·前端
荒诞硬汉18 小时前
JavaBean相关补充
java·开发语言
提笔忘字的帝国18 小时前
【教程】macOS 如何完全卸载 Java 开发环境
java·开发语言·macos
2501_9418824818 小时前
从灰度发布到流量切分的互联网工程语法控制与多语言实现实践思路随笔分享
java·开发语言
華勳全栈19 小时前
两天开发完成智能体平台
java·spring·go