使用Spring异步任务

news/2024/11/22 21:35:43/

在实际的应用开发中,异步任务能够显著提高系统的响应性能和并发处理能力。Spring 框架为异步任务的实现提供了强大且便捷的支持。

异步任务的使用与注意事项

一、核心注解与配置

@Async需要和@EnableAsync一起使用才有效果

@Async注解

@Async 注解用于标记需要异步执行的方法。当方法被标记为 @Async 后,调用者在调用时会立即返回,方法的实际执行会被提交到 Spring task execute的任务执行机制中,由指定的线程池中的线程来负责执行。

@EnableAsync注解

@EnableAsync 注解需要标注在配置类上,用于启用异步方法执行的支持。只有当两者配合使用时,才能实现异步任务的功能。

二、异步任务的返回值

异步任务可以有返回值,也可以没有返回值。

  1. 无返回值的异步任务,专注于执行特定的业务逻辑,不向调用者返回结果。
  2. 有返回值的异步任务,通过返回 Future 对象,调用方能够在后续的合适时机获取异步执行的结果,或者基于这个 Future 对象进行其他相关操作,比如判断任务是否完成、获取结果等。

三、自定义线程池和异常处理器**

异步任务使用的是Spring定义的线程池SimpleAsyncTask,其实它不是真正的线程池,这个类不会重用线程,默认每次调用都会创建一个新的线程,效率是比较低的,而且不能很好的匹配业务特点。所以我们通常在使用异步任务的时候都会根据业务的特点去自定义线程池。

示例代码

(一)配置类中自定义线程池和异常处理器

java">package com.imooc.engineering.guide.async;import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;import java.lang.reflect.Method;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;@Slf4j
@EnableAsync
@Configuration
public class AsyncConfiguration implements AsyncConfigurer {/*** 自定义异步任务执行器* 在异步任务执行时使用这个自定义配置的线程池* @return*/@Overridepublic Executor getAsyncExecutor() {int cpuCount = Runtime.getRuntime().availableProcessors();  // 获取当前系统的CPU核心数ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();taskExecutor.setCorePoolSize(cpuCount * 2); // 核心线程池大小为CPU核心数的2倍taskExecutor.setMaxPoolSize(cpuCount * 4);  // 最大线程池大小为CPU核心数的4倍taskExecutor.setQueueCapacity(50000);       // 队列容量为50000taskExecutor.setKeepAliveSeconds(60);       // 线程存活时间为60秒taskExecutor.setThreadNamePrefix("async-test:");//拒绝策略taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());   // 当任务提交时如果线程池已满且队列已满,则直接丢弃新提交的任务。taskExecutor.setWaitForTasksToCompleteOnShutdown(true);     // 在关闭时等待任务完成taskExecutor.setAwaitTerminationSeconds(60);        // 等待任务完成的超时时间为60秒taskExecutor.initialize();      // 初始化线程池return taskExecutor;}/*** 处理异步任务中的未捕获异常* @return*/@Overridepublic AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {return new AsyncUncaughtExceptionHandler(){/*** 当异步任务发生未捕获的异常时执行* @param ex* @param method* @param params*/@Overridepublic void handleUncaughtException(Throwable ex, Method method, Object... params) {log.error("async error:");}};}
}

(二)定义异步任务的接口与实现

接口

java">package com.imooc.engineering.guide.async;import java.util.concurrent.Future;/*** 异步任务服务接口定义*/
public interface IAsyncService {void asyncMethodNoReturn() throws InterruptedException;Future<String> asyncMethodHasReturn() throws InterruptedException;}

实现类

java">package com.imooc.engineering.guide.async;import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;import java.util.concurrent.Future;@Slf4j
@Service
public class AsyncServiceImpl implements IAsyncService{/*** 异步任务没有返回值* @return* @throws InterruptedException*/@Async@Overridepublic void asyncMethodNoReturn() throws InterruptedException {// 业务逻辑}/*** 异步任务有返回值* @return* @throws InterruptedException*/@Async@Overridepublic Future<String> asyncMethodHasReturn() throws InterruptedException {//业务逻辑return new AsyncResult<>("haha");}
}

异步方法可以有返回值也可以没有返回值,asyncMethodNoReturn()定义了一个没有返回值的方法。
asyncMethodHasReturn()定义了一个有返回值的方法,返回Future 对象,调用方可以通过这个 Future 对象来获取异步执行的结果或者进行其他相关的操作,比如判断任务是否完成、获取结果等。

@Async 的原理及失效场景

原理

@Async 的原理基于 Spring AOP 的动态代理机制实现。在 Spring 容器启动并初始化 Bean 时,会检查类中是否标注了 @Async 注解。若有标注,将为其创建切入点和切入点处理器,并依据切入点生成代理。当线程调用被 @Async 注解标注的方法时,会触发代理,执行切入点处理器的 invoke 方法,将方法的执行交付给线程池中的另一个线程处理,以此达成异步执行的效果。

失效场景

一个方法调用了本类中被 @Async注解的另一个方法时,没有经过Spring AOP的代理类,此时不会异步执行。所以,只有在外部调用带有 @Async 注解方法的时候才会生效,在同一类中的方法调用带有 @Async 注解的方法无法实现异步,这种情形仍然是同步执行。


http://www.ppmy.cn/news/1549130.html

相关文章

985研一学习日记 - 2024.11.17

一个人内耗&#xff0c;说明他活在过去&#xff1b;一个人焦虑&#xff0c;说明他活在未来。只有当一个人平静时&#xff0c;他才活在现在。 日常 1、起床6:00 2、健身2h 3、LeetCode刷了1题 动态规划&#xff1a;01背包理论基础 掌握01背包和完全背包&#xff0c;就够用了…

初识Linux · 信号处理 · 续

目录 前言&#xff1a; 可重入函数 重谈进程等待和优化 前言&#xff1a; 在前文&#xff0c;我们已经介绍了信号产生&#xff0c;信号保存&#xff0c;信号处理的主题内容&#xff0c;本文作为信号处理的续篇&#xff0c;主要是介绍一些不那么重要的内容&#xff0c;第一个…

数据结构哈希表-(开放地址法+二次探测法解决哈希冲突)(创建+删除+插入)+(C语言代码)

#include<stdio.h> #include<stdlib.h> #include<stdbool.h> #define M 20 #define NULLDEL -1 #define DELDEY -2typedef struct {int key;int count; }HashTable;//创建和插入 void Insert(HashTable ha[], int m, int p, int key) {int i, HO, HI;HO key…

vim 一次注释多行 的几种方法

在 Vim 中一次注释多行是一个常见操作。可以使用以下方法根据你的具体需求选择合适的方式&#xff1a; 方法 1&#xff1a;手动插入注释符 进入正常模式&#xff1a; 按 Esc 确保进入正常模式。 选择需要注释的多行&#xff1a; 移动到第一行&#xff0c;按下 Ctrlv 进入可视块…

kafka中是如何快速定位到一个offset的

定位到具体的segment日志文件&#xff0c;采用二分法先定位到index索引文件计算查找的offset在日志文件的相对偏移量 1、分区和日志段&#xff1a; 每个主题的分区&#xff08;Partition&#xff09;被划分为多个日志段&#xff08;Log Segment&#xff09;。每个日志段是一个…

GPT promote 论文学术润色提示词

学术写作的润色 01 我正在为某知名[学科]学术期刊撰写一篇关于[主题]的论文。我在以下部分试图表达的是[具体观点]。请重新措辞&#xff0c;使之清晰、连贯、简洁&#xff0c;确保每段之间衔接流畅。去除口语化的内容&#xff0c;使用专业化语气。 Im writing a paper on [t…

MySQL:表的约束

目录 一. 表的约束和约束的目标 二. 空属性 三. 默认值default 四. 列描述 五. zerofill 六. 主键 6.1 建表时定义主键 6.2 去掉主键 6.3 建表后添加主键 6.4 复合主键 七. 自增长 八. 唯一键 九. 外键 一. 表的约束和约束的目标 表…

ATmaga8单片机Pt100温度计源程序+Proteus仿真设计

目录 1、项目功能 2、仿真图 ​3、程序 资料下载地址&#xff1a;ATmaga8单片机Pt100温度计源程序Proteus仿真设计 1、项目功能 设计Pt100铂电阻测量温度的电路&#xff0c;温度测量范围是0-100摄氏度&#xff0c;要求LCD显示。画出电路图&#xff0c;标注元器件参数&am…