异步编排

源码位置: GitHub

CompletableFuture 的详解

它就是创建一个异步任务,然后在干什么,可以使用多任务组合

  • 创建任务的方法
static CompletableFuture<Void> runAsync(Runnable runnable)
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
  • 然后继续上一段的任务(里面包含了串行,AND,OR)

串行:

public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor) public CompletionStage<Void> thenAccept(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action,Executor executor); public CompletionStage<Void> thenRun(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action,Executor executor);
Function<? super T,? extends U>
T:上一个任务返回结果的类型
U:当前任务的返回值类型

参数解析:

thenApply 方法:当一个线程依赖另一个线程时,获取上一个任务返回的结果,并返回当前任务的返回值。(接收上一阶段任务结果,返回结果)

thenAccept方法:消费处理结果。接收任务的处理结果,并消费处理,无返回结果。  (接收上一阶段任务结果,不返回结果)

thenRun方法:不接收上一阶段任务结果,并且无返回值

带有Async默认是异步执行的。这里所谓的异步指的是不在当前线程内执行。

AND

public <U,V> CompletionStage<V> thenCombineAsync (CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn,Executor executor);
- 上一阶段任务与other任务均执行结束,接收两个任务的结果,并可获取返回值 public <U> CompletionStage<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn,
Executor executor);
- 使用上一阶段任务的结果,返回一个新的CompletableFuture实例

更多的参数详解: [博客链接](CompletableFuture 异步编排 - 掘金 (juejin.cn))

  • 多任务组合
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs);

public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs);

代码测试

配置类的引入

yml

thread:
pool:
corePoolSize: 4
maxPoolSize: 8
workQueue: 25
keepAliveTime: 30

config

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor; @Configuration
@ConfigurationProperties(prefix = "thread.pool")
public class AsyncConfig{
//核心线程数量大小
private int corePoolSize = 4;
//线程池最大容纳线程数
private int maxPoolSize =8;
//阻塞队列
private int workQueue = 25;
//线程空闲后的存活时长
private int keepAliveTime = 30; @Bean("asyncTaskExecutor")
public Executor 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;
}
}

参数详解: 建议看看下面的线程池对拒绝任务(无线程可用)的处理策略

    1、corePoolSize:核心线程数
* 核心线程会一直存活,及时没有任务需要执行
* 当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理
* 设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭 2、queueCapacity:任务队列容量(阻塞队列)
* 当核心线程数达到最大时,新任务会放在队列中排队等待执行 3、maxPoolSize:最大线程数
* 当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务
* 当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常 4、 keepAliveTime:线程空闲时间
* 当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
* 如果allowCoreThreadTimeout=true,则会直到线程数量=0 5、allowCoreThreadTimeout:允许核心线程超时
6、rejectedExecutionHandler:任务拒绝处理器
* 两种情况会拒绝处理任务:
- 当线程数已经达到maxPoolSize,切队列已满,会拒绝新任务
- 当线程池被调用shutdown()后,会等待线程池里的任务执行完毕,再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务
* 线程池会调用rejectedExecutionHandler来处理这个任务。如果没有设置默认是AbortPolicy,会抛出异常
* ThreadPoolExecutor类有几个内部实现类来处理这类情况:
- AbortPolicy 丢弃任务,抛运行时异常(默认)
- CallerRunsPolicy 执行任务
- DiscardPolicy 忽视,什么都不会发生
- DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务
* 实现RejectedExecutionHandler接口,可自定义处理器

Demo1

        CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> {
System.out.println("洗水壶");
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
return "水壶";
}).thenApply(e->{
System.out.println("烧水");
try {
Thread.sleep(5000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
return "热水";
});
//洗水壶->洗水杯->拿茶叶
CompletableFuture<String> task2 = CompletableFuture.supplyAsync(() -> {
System.out.println("洗茶壶");
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
return "茶壶";
}).thenApply(e->{
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("洗水杯");
return "水杯";
}).thenApply(e->{
System.out.println("拿茶叶");
return "茶叶";
});
//泡茶
CompletableFuture<String> task3 = task1.thenCombine(task2, (a, b) -> {
System.out.println("泡茶");
return "茶";
});
String tea = task3.join();
System.out.println(tea);

参数解析:

更多的参数详解: [博客链接](CompletableFuture 异步编排 - 掘金 (juejin.cn))

Demo2

  • 这个测试我就没有写了,自己可以看看

问题:当查询接口较复杂时候,数据的获取都需要远程调用,必然需要花费更多的时间。

假如查询文章详情页面,需要如下标注的时间才能完成:


// 1. 查询文章详情 0.5s // 2. 查询文章博主个人信息 0.5s // 3. 查询文章评论 1s // 4. 查询博主相关文章分类 1s // 5. 相关推荐文章 1s // ......
上面的描述只是举个例子不要在意这里的查询描述,看实际情况使用,有些相关的查询我们可以拆分接口实现,上面的描述只是为了举例子。
@Service
public class ArticleService {
@Autowired
private ArticleClient articleClient;
@Autowired
private UserClient userClient;
@Autowired
private ThreadPoolExecutor threadPoolExecutor; public ItemVo load(Long id) {
// 1. 查询文章详情 0.5s
// 下面的查询需要用到文章对应的发布用户,所以这里需要使用CompletableFuture.supplyAsync
CompletableFuture<ArticleEntity> articleCompletableFuture = CompletableFuture.supplyAsync(() -> {
ResponseVo<ArticleEntity> skuEntityResponseVo = this.articleClient.getArticleById(id);
ArticleEntity articleEntity = skuEntityResponseVo.getData();
if (articleEntity == null) {
return null;
}
itemVo.setId(id);
itemVo.setTitle(articleEntity.getTitle());
itemVo.setDefaltImage(articleEntity.getDefaultImage());
return articleEntity;
}, threadPoolExecutor);
// 2. 查询文章博主个人信息 0.5s
// 这里查询需要依赖文章关联的用户id,所以需要使用articleCompletableFuture.thenAcceptAsync()
CompletableFuture<Void> userCompletableFuture = articleCompletableFuture.thenAcceptAsync(articleEntity -> {
ResponseVo<UserEntity> categoryResponseVo = this.userClient.queryUserInfoById(articleEntity.getUserId());
UserEntity userEntity = categoryResponseVo.getData();
itemVo.setUserInfo(userEntity);
}, threadPoolExecutor);
// 3. 查询博主相关文章分类 1s
// 这里查询需要依赖文章关联的用户id,所以需要使用articleCompletableFuture.thenAcceptAsync()
CompletableFuture<Void> userOtherArticleCompletableFuture = articleCompletableFuture.thenAcceptAsync(articleEntity -> {
ResponseVo<List<UserAuserOtherArticleEntity>> categoryResponseVo = this.articleClient.queryUserAuserOtherArticleById(articleEntity.getUserId());
UserAuserOtherArticleEntity userAuserOtherArticleEntity = categoryResponseVo.getData();
itemVo.setUserAuserOtherArticleList(userAuserOtherArticleEntity);
}, threadPoolExecutor);
// 4. 查询文章评论 1s
// 不需要依赖其他请求返回值,可以使用新的异步对象 CompletableFuture.runAsync()
CompletableFuture<Void> commentsCompletableFuture = CompletableFuture.runAsync(() -> {
ResponseVo<List<UserArticleCategoryEntity>> userArticleCategoryVo = this.userClient.queryCommentsByArticleId(id);
UserArticleCategoryEntity userArticleCategoryEntity = userArticleCategoryVo.getData();
itemVo.setUserArticleCategoryList(userArticleCategoryEntity);
}, threadPoolExecutor);
// 5. 相关推荐文章 1s
// 不需要依赖其他请求返回值,可以使用新的异步对象 CompletableFuture.runAsync()
CompletableFuture<Void> relatedArticlesCompletableFuture = CompletableFuture.runAsync(() -> {
ResponseVo<List<RelatedArticlesEntity>> userArticleCategoryVo = this.articleClient.queryRelatedArticles(id);
UserArticleCategoryEntity userArticleCategoryEntity = userArticleCategoryVo.getData();
itemVo.setUserArticleCategoryList(userArticleCategoryEntity);
}, threadPoolExecutor);
}
// 多任务执行组合 CompletableFuture.allOf()
CompletableFuture.allOf(articleCompletableFuture, userCompletableFuture, userOtherArticleCompletableFuture,
commentsCompletableFuture, relatedArticlesCompletableFuture).join();
return itemVo;
}

CompletableFuture的async后缀函数与不带async的函数的区别

参考链接: [博客链接]((106条消息) CompletableFuture的async后缀函数与不带async的函数的区别_leon_wzm的博客-CSDN博客_thenacceptasync)

结论:

不带async的函数的动作比较复杂

f的whenComplete的内容由哪个线程来执行,取决于哪个线程X执行了f.complete()。但是当X线程执行了f.complete()的时候,whenComplete还没有被执行到的时候(就是事件还没有注册的时候),那么X线程就不会去同步执行whenComplete的回调了。这个时候哪个线程执行到了whenComplete的事件注册的时候,就由哪个线程自己来同步执行whenComplete的事件内容。

而whenCompleteAsync的场合,就简单很多。一句话就是线程池里面拿一个空的线程或者新启一个线程来执行回调。和执行f.complete的线程以及执行whenCompleteAsync的线程无关。

ThreadPoolTaskExecutor 和 ThreadPoolExecutor 的区别

参考链接: [博客链接]((106条消息) ThreadPoolTaskExecutor 和 ThreadPoolExecutor 的区别_PonderYao的博客-CSDN博客_threadpooltaskexecutor和threadpoolexecutor)

结论:

其实最主要的原因很直观:ThreadPoolExecutor是一个不受Spring管理生命周期、参数装配的Java类,而有了ThreadPoolTaskExecutor的封装,线程池才有Spring“内味”。

Spring 线程池的使用

业务使用多线程的原因

  • 目的是面对高并发的时候,提高运行速度

场景一:

一个业务逻辑有很多次的循环,每次循环之间没有影响,比如验证1万条url路径是否存在,正常情况要循环1万次,逐个去验证每一条URL,这样效率会很低,假设验证一条需要1分钟,总共就需要1万分钟,有点恐怖。这时可以用多线程,将1万条URL分成50等份,开50个线程,没个线程只需验证200条,这样所有的线程执行完是远小于1万分钟的。

场景二:

需要知道一个任务的执行进度,比如我们常看到的进度条,实现方式可以是在任务中加入一个整型属性变量(这样不同方法可以共享),任务执行一定程度就给变量值加1,另外开一个线程按时间间隔不断去访问这个变量,并反馈给用户。总之使用多线程就是为了充分利用cpu的资源,提高程序执行效率,当你发现一个业务逻辑执行效率特别低,耗时特别长,就可以考虑使用多线程。

问题:不过CPU执行哪个线程的时间和顺序是不确定的,即使设置了线程的优先级,因此使用多线程的风险也是比较大的,会出现很多预料不到的问题,一定要多熟悉概念,多构造不同的场景去测试才能够掌握!

项目中可以通过:

@Order()
设置运行的优先级,数字越小,级别越高

FutureTask介绍

参考: [博客链接]((101条消息) FutureTask详解_索码理的博客-CSDN博客_futuretask)

线程池为什么要使用阻塞队列

阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入wait 状态,释放 cpu 资源,当队列中有任务时才唤醒对应线程从队列中取出消息进行执行。
使得在线程不至于一直占用cpu资源。(线程执行完任务后通过循环再次从任务队列中取出任务进行执行,代码片段如:while (task != null || (task = getTask()) != null) {})。 不用阻塞队列也是可以的,不过实现起来比较麻烦而已,有好用的为啥不用呢

Spring 常用的线程池的使用

序列

Spring 通过任务执行器(TaskExecutor)来实现多线程和并发编程,使用 ThreadPoolTaskExecutor 实现一个基于线程池的TaskExecutor,

还得需要使用 @EnableAsync 开启异步,并通过在需要的异步方法那里使用注解@Async声明是一个异步任务

Spring 已经实现的异常线程池:

- SimpleAsyncTaskExecutor:不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。

- SyncTaskExecutor:这个类没有实现异步调用,只是一个同步操作。只适用于不需要多线程的地方

- ConcurrentTaskExecutor:Executor的适配类,不推荐使用。如果ThreadPoolTaskExecutor不满足要求时,才用考虑使用这个类

- SimpleThreadPoolTaskExecutor:是Quartz的 SimpleThreadPool 的类。线程池同时被quartz和非quartz使用,才需要使用此类

- ThreadPoolTaskExecutor :最常使用,推荐。 其实质是对 java.util.concurrent.ThreadPoolExecutor 的包装

扩展:相信大家在 Java 里面也学过 JUC ,里面有 Java 里面的线程池,可以直接去看看 ThreadPoolExecutor

至于为什么有线程池,Spring 为什么还有在自己搞一个,可以自己去探索,Spring 的底层还是 ThreadPoolExecutor ,只是它的生命周期不受它控制。

常规使用

//    线程池(config里面的Bean)
@Autowired
private Executor taskExecutor; Callable<ScyTeacher> scy =()-> scyTeacherMapper.selectOne(new LambdaQueryWrapper<ScyTeacher>()
.eq(ScyTeacher::getUsername,test));
FutureTask<ScyTeacher> commentCallable = new FutureTask<>(scy);
Future<Map> submit = executor.submit(commentCallable);
Map map = submit.get();

异步使用

记得开启异步(配置类添加)

@EnableAsync //开启异步执行
package com.melodyjerry.thread;

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service; /**
* @classname AsyncTaskService
* @description 异步任务的执行类
*/
@Service
public class AsyncTaskService {
@Async //异步方法
public void executeAsyncTask(Integer i) {
System.out.javaprintln("执行异步任务: "+i);
} @Async //异步方法
public void executeAsyncTaskPlus(Integer i) {
System.out.println("执行异步任务+1: " + (i+1));
}
}

异步编排 Spring(线程池)的更多相关文章

  1. spring线程池的同步和异步(1)

    spring线程池(同步.异步) 一.spring异步线程池类图 二.简单介绍 2.1. TaskExecutor---Spring异步线程池的接口类,其实质是java.util.concurrent ...

  2. Aysnc的异步执行的线程池

    ProxyAsyncConfiguration.java源码: @Configuration @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public clas ...

  3. Spring线程池配置模板设计(基于Springboot)

    目录 线程池配置模板 基础的注解解释 常用配置参数 配置类设计 线程池使用 ThreadPoolTaskExecutor源码 线程池配置模板 springboot给我们提供了一个线程池的实现,它的底层 ...

  4. JDK线程池和Spring线程池的使用

    JDK线程池和Spring线程池实例,异步调用,可以直接使用 (1)JDK线程池的使用,此处采用单例的方式提供,见示例: public class ThreadPoolUtil { private s ...

  5. Spring线程池开发实战

    Spring线程池开发实战 作者:chszs,转载需注明. 作者博客主页:http://blog.csdn.net/chszs 本文提供了三个Spring多线程开发的例子,由浅入深,由于例子一目了然, ...

  6. node源码详解(七) —— 文件异步io、线程池【互斥锁、条件变量、管道、事件对象】

    本作品采用知识共享署名 4.0 国际许可协议进行许可.转载保留声明头部与原文链接https://luzeshu.com/blog/nodesource7 本博客同步在https://cnodejs.o ...

  7. Spring线程池ThreadPoolTaskExecutor配置及详情

    Spring线程池ThreadPoolTaskExecutor配置及详情 1. ThreadPoolTaskExecutor配置 <!-- spring thread pool executor ...

  8. 分享知识-快乐自己:Spring线程池配置

    Spring通过ThreadPoolTaskExecutor实现线程池技术,它是使用jdk中的Java.util.concurrent.ThreadPoolExecutor进行实现. Spring 配 ...

  9. Spring线程池由浅入深的3个示例

    作者博客主页:http://blog.csdn.net/chszs 本文提供了三个Spring多线程开发的例子,由浅入深,由于例子一目了然,所以并未做过多的解释.诸位一看便知. 前提条件: 1)在Ec ...

  10. 【SSM Spring 线程池 OJ】 使用Spring线程池ThreadPoolTaskExecutor

    最近做的Online Judge项目,在本地判题的实现过程中,遇到了一些问题,包括多线程,http通信等等.现在完整记录如下: OJ有一个业务是: 用户在前端敲好代码,按下提交按钮发送一个判题请求给后 ...

随机推荐

  1. uniapp+.net core 小程序获取手机号

    获取手机号 从基础库 2.21.2 开始,对获取手机号的接口进行了安全升级,以下是新版本接口使用指南.(旧版本接口目前可以继续使用,但建议开发者使用新版本接口,以增强小程序安全性) 因为需要用户主动触 ...

  2. 从原理剖析带你理解Stream

    摘要:Stream是jdk1.8给我们提供的新特性 本文分享自华为云社区<深入理解Stream之原理剖析>,作者: 李哥技术 . Stream是jdk1.8给我们提供的新特性,主要就是允许 ...

  3. 批量获取代理ip

    获取站大爷免费代理ip,然后打印出来,也可以把他存放在其他容器中 # coding:utf-8 import requests, re requests.packages.urllib3.disabl ...

  4. 在hyper-v虚拟机中安装并配置linux

    虽然都是自己写的,还是贴个原文链接吧,如果文章里的图片错乱了,可能就是我贴错了,去看原文吧. 多图警告 WSL2真香? WSL2相比于WSL1前者更类似于虚拟机,配合上Windoes Terminal ...

  5. KingbaseES 约束

    目录 什么是约束 如何定义约束 列约束 表约束 为约束创建名称 默认约束名称 自定义约束名称 KingbaseES 的可用约束列表 CHECK约束 非空约束 UNIQUE约束 PRIMARY KEY约 ...

  6. Django 创建 APP和目录结构介绍

    一.通过pip安装Django 以windows 系统中使用pip命令安装为例 win+r,调出cmd,运行命令:pip install django自动安装PyPi 提供的最新版本.指定版本,可使用 ...

  7. CPU密集型和IO密集型(判断最大核心线程的最大线程数)

    CPU密集型和IO密集型(判断最大核心线程的最大线程数) CPU密集型 1.CPU密集型获取电脑CPU的最大核数,几核,最大线程数就是几Runtime.getRuntime().availablePr ...

  8. Logstash:运用 fingerprint 过滤器处理重复的文档

    文章转载自:https://blog.csdn.net/UbuntuTouch/article/details/106639848 背景:Elasticsearch 索引 在介绍重复数据删除解决方案之 ...

  9. CentOS使用yum方式安装yarn和nodejs

    # 使用epel-release.repo源安装的nodejs版本是6.17.1,有些前端项目使用的话会提示版本太低,具体下图 # 命令执行后的详细情况:curl -sL https://rpm.no ...

  10. Portainer实用教程

    Portainer使用 Nginx 容器实现端口转发 在 WordPress 部署完成后,需要在浏览器内输入 IP:端口或域名:端口 的形式访问网站,但我们一般访问应用的时候都是希望不加端口就能访问域 ...