Java异步编程CompletableFuture(串行,并行,批量执行)

🍓 简介:java系列技术分享(👉持续更新中…🔥)
🍓 初衷:一起学习、一起进步、坚持不懈
🍓 如果文章内容有误与您的想法不一致,欢迎大家在评论区指正🙏
🍓 希望这篇文章对你有所帮助,欢迎点赞 👍 收藏 ⭐留言 📝

🍓 更多文章请点击
在这里插入图片描述在这里插入图片描述

图1

一、什么是CompletableFuture?

    在Java中CompletableFuture用于异步编程,异步编程是编写非阻塞的代码,运行的任务在一个单独的线程,与主线程隔离,并且会通知主线程它的进度,成功或者失败。在这种方式中,主线程不会被阻塞,不需要一直等到子线程完成。主线程可以并行的执行其他任务。 使用这种并行方式,可以极大的提高程序的性能。

CompletableFuture 是 Java 8 引入的一个类,用于简化异步编程模型。它是 Future 接口的一个增强版本,提供了更加丰富的功能和更灵活的用法

   CompletableFuture是一个Java库中的类,用于处理异步编程。它提供了一种简化异步操作的方式,可以更方便地编写异步代码,并处理异步任务的结果。

   使用CompletableFuture可以简化异步编程的复杂性,并提供更灵活和强大的功能。它是Java 8中新增的功能之一,为开发者提供了更好的异步编程体验。

CompletableFuture可以用于以下几种情况:

  • 异步执行任务:可以使用CompletableFuture来执行一个耗时的任务,而不会阻塞主线程。

  • 组合多个异步任务的结果:可以将多个CompletableFuture串联起来,以便在它们都完成后执行一些操作。

  • 处理异常情况:可以使用CompletableFuture来处理异步任务中可能出现的异常情况。

  • 并发执行多个任务 :可以使用CompletableFuture来同时执行多个任务,并等待它们全部完成。

二、CompletableFuture的结构

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

CompletableFuture它同时实现了FutureCompletionStage两个接口。

  • Future : 它代表一个异步计算的结果。它可以用于提交一个任务并在未来的某个时间获取它的结果。Future提供了一些方法来检查任务的完成状态、取消任务以及获取任务的结果。
  • CompletionStage : CompletionStage是Java 8引入的一个接口,它扩展了Future的功能,并提供了更强大的异步编程支持。它表示一个异步计算的阶段,可以将多个阶段链接在一起形成一个复杂的异步计算流水线。CompletionStage提供了一系列方法来描述和组合异步计算的各个阶段,例如thenApply、thenCompose、thenCombine等
  • CompletableFuture : 是Java 8中新增加的一个类,它实现了Future和CompletionStage两个接口。CompletableFuture提供了更加灵活和强大的异步编程模型,可以用于处理复杂的异步计算场景。它提供了丰富的方法来处理异步计算的结果、执行转换操作、组合多个异步计算等。通过CompletableFuture,我们可以更加方便地编写异步代码,实现更高效的并发编程。

三、基础方法介绍

因为方法非常多,这里我对常用的进行介绍

3.1 get()join()isDone方法介绍

  • get()join() 都会等待异步操作完成并返回结果,但 get() 在等待期间线程被中断时会抛出异常,而 join() 不会。
  • isDone() 用于检查异步操作是否完成,而不等待操作完成,适用于非阻塞的轮询场景。

3.2 使用 runAsync() 开启一个子线程去执行无结果

// Using Lambda Expression
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
   
   
    try {
   
   
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
   
   
        throw new IllegalStateException(e);
    }
    System.out.println("I'll run in a separate thread than the main thread.");
});

3.3 使用 supplyAsync() 开启一个子线程去执行有返回结果

// Using Lambda Expression
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
   
   
    try {
   
   
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
   
   
        throw new IllegalStateException(e);
    }
    return "Result of the asynchronous computation";
});

System.out.println("结果 : "+future.get());

3.4 CompletableFuture API 的所有方法都有两个重载的方法一个接受Executor线程池

static CompletableFuture<Void>  runAsync(Runnable runnable)
static CompletableFuture<Void>  runAsync(Runnable runnable, Executor executor)
static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)

你可能想知道,runAsync() supplyAsync()方法在单独的线程中执行他们的任务。但是我们不会永远只创建一个线程。CompletableFuture可以从全局的ForkJoinPool.commonPool()获得一个线程中执行这些任务。但是你也可以创建一个线程池并传给runAsync() 和supplyAsync()方法来让他们从线程池中获取一个线程执行它们的任务。

这里使用了简单的线程池创建

Executor executor = Executors.newFixedThreadPool(10);
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
   
   
    try {
   
   
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
   
   
        throw new IllegalStateException(e);
    }
    return "Result of the asynchronous computation";
}, executor);

3.4.1 线程池工具类

可以注入线程池,进行传递给runAsync() 和supplyAsync()方法来让他们从线程池中获取一个线程执行它们的任务

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.ThreadPoolExecutor;

@Configuration
public class MongoThreadPoolConfig {
   
   
    //参数初始化
    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    //核心线程数量大小
    private static final int corePoolSize = Math.max(2, Math.min(CPU_COUNT - 1, 4));
    //线程池最大容纳线程数
    private static final int maxPoolSize = CPU_COUNT * 2 + 1;
    //阻塞队列
    private static final int workQueue = 20;
    //线程空闲后的存活时长
    private static final int keepAliveTime = 30;

    @Bean("asyncTaskExecutor")
    public ThreadPoolTaskExecutor getAsyncExecutor() {
   
   
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        //核心线程数
        threadPoolTaskExecutor.setCorePoolSize(corePoolSize);
        //最大线程数
        threadPoolTaskExecutor.setMaxPoolSize(maxPoolSize);
        //等待队列
        threadPoolTaskExecutor.setQueueCapacity(workQueue);
        //线程前缀
        threadPoolTaskExecutor.setThreadNamePrefix("taskExecutor-");
        //线程池维护线程所允许的空闲时间,单位为秒
        threadPoolTaskExecutor.setKeepAliveSeconds(keepAliveTime);
        // 线程池对拒绝任务(无线程可用)的处理策略
        threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        threadPoolTaskExecutor.initialize();
        return threadPoolTaskExecutor;
    }
}

四、CompletableFuture常用方法介绍

消息打印小工具类(用不用都行)

主要观察和学习CompletableFuture方法

import java.util.StringJoiner;
/**
 * 小工具
 */
`CompletableFuture` 是Java 8中引入的一个并行流处理库,用于异步编程,特别是处理串行和并发任务的组合。`supplyAsync` 方法用于将一个函数(Supplier)的结果异步地提供给未来(Future)。然而,`supplyAsync` 并不是总是能自动优化执行时间,优化主要取决于几个因素: 1. **CPU核心和线程池大小**:如果任务数量超过了系统的可用线程数,那么任务可能会被阻塞,直到有线程可用。如果线程池大小设置得当,可以最大化利用多核处理器的性能。 2. **任务并行性**:`supplyAsync` 默认使用ForkJoinPool来执行任务。如果任务本身是非阻塞的,且它们之间相互独立,那么可以受益于并行化。但如果任务之间存在依赖,过度并行可能反而降低效率。 3. **任务执行时间**:如果任务执行时间很短,那么即使在等待期间,其他任务也可能已经完成,导致整体执行时间并不明显优化。 4. **调度策略**:`supplyAsync` 中的任务会被添加到线程池的工作队列,调度策略(如优先级、时间片轮转等)会影响任务何时被执行。 5. **上下文切换开销**:频繁的上下文切换也会影响执行时间,特别是在任务间交互或IO密集型操作中。 如果你想优化`supplyAsync`的执行时间,可以考虑以下策略: - **调整线程池配置**:根据系统资源和预期负载动态调整线程池大小。 - **批处理**:对于大量的小任务,可以考虑使用`thenAcceptMany`或`thenApplyToLong/Double/Int`来批量提交。 - **优化任务**:减少不必要的同步和计算,使任务执行更快。 - **使用恰当的并发级别**:避免过度并发,尤其是在数据结构修改操作中,过多的并发可能导致数据一致性问题。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dream_sky分享

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值