Spring Boot @Async 注解深度指南

news2025/2/27 23:24:48

Spring Boot @Async 注解深度指南


一、核心使用要点
  1. 启用异步支持

    • 必须在启动类或配置类添加 @EnableAsync,否则异步不生效。
    @SpringBootApplication
    @EnableAsync
    public class Application { ... }
    
  2. 线程池配置

    • 默认问题:Spring 默认使用 SimpleAsyncTaskExecutor(每次新建线程),生产环境需自定义线程池
    • 推荐配置:通过 ThreadPoolTaskExecutor 定义核心参数(核心线程数、队列容量等):
      @Configuration
      @EnableAsync
      public class AsyncConfig implements AsyncConfigurer {
          @Override
          public Executor getAsyncExecutor() {
              ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
              executor.setCorePoolSize(5);
              executor.setMaxPoolSize(10);
              executor.setQueueCapacity(100);
              executor.setThreadNamePrefix("Async-");
              executor.initialize();  // 必须初始化
              return executor;
          }
      }
      
  3. 方法调用限制

    • 同类调用失效禁止在同一个类中直接调用 @Async 方法(如 this.asyncMethod()),需通过代理对象调用。
    • 解决方案:将异步方法拆分到独立类中,通过依赖注入调用:
      @Service
      public class ServiceA {
          @Autowired
          private ServiceB serviceB;  // 异步方法在 ServiceB 中定义
         
          public void callAsync() {
              serviceB.asyncMethod();  // 通过代理对象调用
          }
      }
      

二、常见失效场景及解决方案
  1. 方法修饰符错误

    • 限制@Async 仅对 public 方法生效,private/static/final 方法无效。
  2. 事务管理冲突

    • 问题:异步方法默认不继承事务上下文,@Transactional 可能失效。
    • 解决方案
      • 在异步方法内部显式管理事务。
      • 使用分布式事务框架(如 Seata)。
  3. 异常处理缺失

    • 默认行为:异步方法抛出的异常不会传播到调用线程,需通过 FutureAsyncUncaughtExceptionHandler 捕获。
    • 全局异常处理配置
      @Override
      public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
          return (ex, method, params) -> 
              log.error("异步方法 {} 异常: {}", method.getName(), ex.getMessage());
      }
      

三、进阶注意事项
  1. 线程上下文传递

    • 问题:异步线程默认不继承主线程的上下文(如 MDC 日志跟踪、SecurityContext)。
    • 解决方案:通过 TaskDecorator 装饰任务,手动传递上下文:
      executor.setTaskDecorator(task -> {
          Map<String, String> context = MDC.getCopyOfContextMap();  // 获取主线程上下文
          return () -> {
              MDC.setContextMap(context);  // 设置到异步线程
              task.run();
              MDC.clear();
          };
      });
      
  2. 与定时任务结合

    • 风险:在 @Scheduled@XxlJob 标注的方法上直接使用 @Async,可能导致调度平台无法感知任务结果。
    • 建议:仅在任务内部耗时操作中使用异步,而非整个方法。
  3. 资源释放

    • 线程池关闭:Spring 管理的 ThreadPoolTaskExecutor 会在应用关闭时自动调用 shutdown(),但需设置等待任务完成:
      executor.setWaitForTasksToCompleteOnShutdown(true);  // 等待任务完成
      executor.setAwaitTerminationSeconds(60);             // 最长等待时间
      

四、实际案例解析
案例 1:基础异步调用(无返回值)

场景:执行耗时任务(如日志记录、消息推送)时不阻塞主线程。
代码实现

@Service
public class NotificationService {
    @Async
    public void sendEmail(String content) {
        System.out.println("异步发送邮件中,线程:" + Thread.currentThread().getName());
        // 模拟耗时操作
        try { Thread.sleep(3000); } 
        catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println("邮件发送完成!");
    }
}

@RestController
public class UserController {
    @Autowired
    private NotificationService notificationService;

    @PostMapping("/register")
    public String registerUser() {
        notificationService.sendEmail("欢迎注册!"); // 异步执行
        return "注册成功,邮件发送中..."; // 主线程立即返回
    }
}

关键点

  • 方法需标记为 public,且类需被 Spring 管理(如 @Service);
  • 主线程调用后立即返回,任务由 SimpleAsyncTaskExecutor 默认线程池执行。

案例 2:带返回值的异步任务

场景:异步执行任务并获取结果(如批量数据处理)。
代码实现

@Service
public class DataProcessService {
    @Async
    public CompletableFuture<List<String>> processData(List<String> data) {
        System.out.println("异步处理数据,线程:" + Thread.currentThread().getName());
        // 模拟耗时处理
        List<String> result = data.stream()
            .map(String::toUpperCase)
            .collect(Collectors.toList());
        return CompletableFuture.completedFuture(result);
    }
}

@RestController
public class DataController {
    @Autowired
    private DataProcessService dataProcessService;

    @GetMapping("/process")
    public CompletableFuture<String> process() {
        return dataProcessService.processData(Arrays.asList("a", "b", "c"))
            .thenApply(result -> "处理结果:" + result);
    }
}

关键点

  • 返回值需用 CompletableFutureFuture 包装;
  • 调用方通过 thenApplyget() 获取结果(注意阻塞风险)。

案例 3:自定义线程池配置

场景:优化线程资源,避免默认线程池的性能问题。
配置类: 注意:这里 使用 @Bean(‘线程池名称’) 注解 或 实现 AsyncConfigurer 可任选其一 也可都实现, 实现AsyncConfigurer 代表将 将spring默认线程池替换为 当前线程池(默认线程池存在问题:“Spring 默认使用 SimpleAsyncTaskExecutor(每次新建线程),生产环境需自定义线程池。”),使用 @Bean() 则需要给当前线程池的指定bean名称,在使用 @Async(‘线程池名称’)注解时,就需要指定线程池的名称了,当然这个案例中这里不是必须的,当@Async不指定线程池名称时,则使用的时是默认线程池,在这个案例中,默认的线程池也就是 ‘customExecutor’。

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
    @Bean("customExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("Custom-Async-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}

使用示例

@Service
public class ReportService {
    @Async("customExecutor") // 指定线程池
    public void generateReport() {
        System.out.println("生成报表中,线程:" + Thread.currentThread().getName());
    }
}

关键点

  • 通过 @Async("beanName") 指定线程池;
  • 拒绝策略推荐 CallerRunsPolicy 避免任务丢失。

案例 4:异步事务管理

场景:异步方法中操作数据库并保证事务一致性。
代码实现

@Service
public class OrderService {
    @Autowired
    private OrderRepository orderRepository;

    @Async
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void asyncCreateOrder(Order order) {
        orderRepository.save(order); // 事务独立提交
        if (order.getAmount() < 0) {
            throw new RuntimeException("金额异常"); // 触发回滚
        }
    }
}

关键点

  • 异步方法需添加 @Transactional 并指定传播行为(如 REQUIRES_NEW);
  • 主线程事务与异步线程事务相互隔离。

案例 5:全局异常处理

场景:捕获异步方法中的未处理异常。

(1)默认行为与风险

1. 无返回值方法异常静默丢弃

例如:

@Async
public void asyncTask() {
    throw new RuntimeException("异步异常"); // 无日志、无处理
}

异常会被 Spring 默认的 SimpleAsyncUncaughtExceptionHandler 处理,仅打印 ERROR 级别日志,但无具体堆栈信息。

2. 调试困难

异步线程的异常不会传播到调用线程,若未记录日志,可能无法发现潜在问题。


(2)最佳实践

1. 无返回值处理异常:

通过自定义异常处理器,统一记录日志或发送告警:

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
  @Override
  public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
      return (ex, method, params) -> {
          // 记录日志或发送告警
          log.error("异步方法 {} 异常,参数: {}", method.getName(), Arrays.toString(params), ex);
          // 发送邮件/企业微信通知(可选)
      };
  }
}
使用示例:
@Service
public class NotificationService {
    @Async
    public void sendEmail() {
        throw new RuntimeException("邮件发送失败");
    }
}
  • 触发场景:调用 sendEmail() 时,异常会被 AsyncUncaughtExceptionHandler 捕获并记录日志。
2. 结合返回值处理异常:
1. 通过 Future.get() 捕获异常

使用 CompletableFuture 包装返回值,调用 get() 时显式捕获异常。

@Service
public class DataService {
    @Async
    public CompletableFuture<String> processData() {
        return CompletableFuture.supplyAsync(() -> {
            if (error) throw new RuntimeException("数据处理异常");
            return "处理结果";
        });
    }
}

@RestController
public class DataController {
    @Autowired
    private DataService dataService;

    @GetMapping("/process")
    public String process() {
        try {
            return dataService.processData().get();
        } catch (InterruptedException | ExecutionException e) {
            return "处理失败: " + e.getCause().getMessage();
        }
    }
}
2. 通过 exceptionally() 链式处理

利用 CompletableFuture 的链式异常处理:

@Async
public CompletableFuture<String> asyncTask() {
    return CompletableFuture.supplyAsync(() -> {
        throw new RuntimeException("任务失败");
    }).exceptionally(ex -> {
        log.error("任务异常", ex);
        return "默认值";
    });
}
注意事项
  • 阻塞风险Future.get() 会阻塞主线程,需结合超时机制(如 get(5, TimeUnit.SECONDS))。
  • 线程池隔离:建议为耗时任务配置独立线程池,避免核心业务线程池被阻塞。

总结与最佳实践

场景策略适用场景
无返回值 + 无需关注结果必须实现 getAsyncUncaughtExceptionHandler日志记录、消息推送等非关键任务
无返回值 + 需关注结果重构为有返回值方法,或实现异常处理器数据同步、状态更新等关键任务
有返回值优先通过 FutureCompletableFuture 处理异常批量处理、复杂计算等需返回结果的任务

最佳实践

  1. 混合使用:对关键任务使用 CompletableFuture 返回值,非关键任务用 void + 全局处理器。
  2. 监控告警:在 AsyncUncaughtExceptionHandler 中集成 Sentry 或 Prometheus 监控。
  3. 事务拆分:异步方法涉及数据库操作时,确保事务边界清晰(如拆分到独立服务)。
  4. 线程池隔离:为耗时任务配置独立线程池,避免核心业务线程池被阻塞。
**案例 6:定时任务异步化
  • 场景:XXL-JOB 任务异步执行,避免阻塞调度线程。
  • 代码示例
    @Component
    @XxlJob("generateTask")
    public class TaskJob {
        @Async
        public void generateTask(String param) {
            // 异步执行耗时任务
        }
    }
    
  • 风险:调度平台无法获取执行结果,需通过日志或回调机制跟踪状态。

五、性能优化与监控
  1. 参数调优

    • 核心公式
      • 核心线程数 ≈ CPU 核心数 × 2
      • 队列容量根据任务平均耗时调整(避免 OOM)。
  2. 拒绝策略选择

    • 生产推荐:使用 CallerRunsPolicy(由调用线程执行任务),避免任务丢失:
      executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
      
  3. 监控指标

    • 关键指标:活跃线程数 (getActiveCount())、队列大小 (getQueue().size())、已完成任务数 (getCompletedTaskCount())。

总结与最佳实践

场景技术方案
简单异步任务无返回值方法 + 默认线程池
结果依赖任务CompletableFuture 包装返回值
高并发优化自定义 ThreadPoolTaskExecutor
数据库事务@Transactional + 独立传播行为
异常处理AsyncUncaughtExceptionHandler

注意事项

  1. 避免同类内调用 @Async 方法(需通过代理对象调用);
  2. 监控线程池状态(活跃线程数、队列堆积)防止资源耗尽;
  3. 异步方法中谨慎使用 ThreadLocal,需通过 TaskDecorator 传递上下文。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2307085.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

取topN不同算法的实现的性能差别

背景 最近在实现一个需求&#xff0c;需要对大量数据中排序出前N&#xff0c;最暴力的方法肯定是直接全量排序。这里很明显是可以不用全量排序的&#xff0c;取前N&#xff0c;我们自然而然可以想到一个算法——堆排序。 一开始自己先写好了一版&#xff0c;后来想起&#xff…

【实战 ES】实战 Elasticsearch:快速上手与深度实践-1.1.2典型应用场景:日志分析、实时搜索、推荐系统

&#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 文章大纲 为什么选择Elasticsearch&#xff1f;——典型应用场景深度解析1. 引言2. 日志分析&#xff1a;海量数据的实时洞察2.1 行业痛点2.2 ES解决方案关键技术实现&#xff1a; 2.…

Spring Cloud Alibaba学习 3- Sentinel入门使用

Spring Cloud Alibaba学习 3- Sentinel入门使用 中文文档参考&#xff1a;Sentinel中文文档 一. SpringCloud整合Sentinel 1.1 下载Sentinel-Dashboard Sentinel下载地址&#xff1a;Sentinel-Dashboard 到下载目录&#xff0c;cmd输入 java -jar sentinel-dashboard-1.8…

使用DeepSeek/chatgpt等AI工具辅助网络协议流量数据包分析

随着deepseek,chatgpt等大模型的能力越来越强大&#xff0c;本文将介绍一下deepseek等LLM在分数流量数据包这方面的能力。为需要借助LLM等大模型辅助分析流量数据包的同学提供参考&#xff0c;也了解一下目前是否有必要继续学习wireshark工具以及复杂的协议知识。 pcap格式 目…

C语言 --- 经典习题1

C语言 --- 经典习题1 第 一 题 - - - 交 换 两 个 整 数 的 值&#xff08;四 种 方 法&#xff09;第 二 题 - - - 最 大 公 约 数 和 最 小 公 倍 数 之 和总结 &#x1f4bb;作者简介&#xff1a;曾 与 你 一 样 迷 茫&#xff0c;现 以 经 验 助 你 入 门 C 语 言 &#x1…

自定义mybatis拦截器,在springboot项目中不起作用的解决方法

自定义mybatis拦截器&#xff0c;在springboot项目中不起作用的解决方法 自定义mybatis拦截器&#xff0c;在若依springboot项目中不起作用的原因 找到 MyBatisConfig 配置类&#xff0c;引入自定义配置 在sqlSessionFactory中添加自定义拦截器&#xff0c;就可以正常使用了…

记录一下在k3s快速创建gitlab

废话不多说&#xff0c;直接上配置文件 需要修改的地方&#xff08;备注都有写&#xff09;&#xff1a; 1.命名空间 namespace 2. claimName 文件挂载 Deployment kind: Deployment apiVersion: apps/v1 metadata:name: gitlabnamespace: cicd # 替换为您的命名空间la…

AWQ和GPTQ量化的区别

一、前言 本地化部署deepseek时发现&#xff0c;如果是量化版的deepseek&#xff0c;会节约很多的内容&#xff0c;然后一般有两种量化技术&#xff0c;那么这两种量化技术有什么区别呢&#xff1f; 二、量化技术对比 在模型量化领域&#xff0c;AWQ 和 GPTQ 是两种不同的量…

线性模型 - 支持向量机

支持向量机&#xff08;SVM&#xff09;是一种用于分类&#xff08;和回归&#xff09;的监督学习算法&#xff0c;其主要目标是找到一个最佳决策超平面&#xff0c;将数据点分为不同的类别&#xff0c;并且使得分类边界与最近的数据点之间的间隔&#xff08;margin&#xff09…

湖北中医药大学谱度众合(武汉)生命科技有限公司研究生工作站揭牌

2025年2月11日&#xff0c;湖北中医药大学&谱度众合&#xff08;武汉&#xff09;生命科技有限公司研究生工作站揭牌仪式在武汉生物技术研究院一楼101会议室举行&#xff0c;湖北中医药大学研究生院院长刘娅教授、基础医学院院长孔明望教授、基础医学院赵敏教授、基础医学院…

面试基础---深入解析 AQS

深入解析 AQS&#xff1a;从源码到实践&#xff0c;剖析 ReentrantLock 和 Semaphore 的实现 引言 在 Java 并发编程中&#xff0c;AbstractQueuedSynchronizer&#xff08;AQS&#xff09;是一个核心框架&#xff0c;它为构建锁和其他同步器提供了基础支持。ReentrantLock 和…

从 0 到 1,用 Python 构建超实用 Web 实时聊天应用

从 0 到 1&#xff0c;用 Python 构建超实用 Web 实时聊天应用 本文深入剖析如何运用 Python 的 Flask 框架与 SocketIO 扩展&#xff0c;搭建一个功能完备的 Web 实时聊天应用。从环境搭建、前后端代码实现&#xff0c;到最终运行展示&#xff0c;逐步拆解关键步骤&#xff0…

Vue2+Element实现Excel文件上传下载预览【超详细图解】

目录 一、需求背景 二、落地实现 1.文件上传 图片示例 HTML代码 业务代码 2.文件下载 图片示例 方式一&#xff1a;代码 方式二&#xff1a;代码 3.文件预览 图片示例 方式一&#xff1a;代码 方式二&#xff1a;代码 一、需求背景 在一个愉快的年后&#xff…

[记录贴] 火绒奇怪的进程保护

最近一次更新火绒6.0到最新版&#xff0c;发现processhacker的结束进程功能无法杀掉火绒的进程&#xff0c;弹窗提示如下&#xff1a; 可能是打开进程时做了权限过滤&#xff0c;火绒注册了两个回调函数如下&#xff1a; 但奇怪的是&#xff0c;在另外一台机器上面更新到最新版…

【蓝桥杯】每天一题,理解逻辑(1/90)【Leetcode 移动零】

文章目录 题目解析讲解算法原理【双指针算法思路】(数组下标充当指针)如何划分和执行过程大致 代码详情 题目解析 题目链接&#xff1a;https://leetcode.cn/problems/move-zeroes/description/ 题目意思解析 把所有的零移动到数组的末尾保持非零元素的相对顺序 理解了这两层…

pycharm远程连接服务器运行pytorch

Linux部署pytorch 背景介绍 不同的开源代码可能需要不同的实验环境和版本&#xff0c;这时候的确体现出Anaconda管理环境的好处了&#xff0c;分别搞一个独立环境方便管理。 有的教程建议选择较旧的版本&#xff0c;但笔者建议在条件允许的情况下安装最新版&#xff0c;本次…

java练习(41)

ps&#xff1a;题目来自力扣 最接近的三数之和 给你一个长度为 n 的整数数组 nums 和 一个目标值 target。请你从 nums 中选出三个整数&#xff0c;使它们的和与 target 最接近。 返回这三个数的和。 假定每组输入只存在恰好一个解。 import java.util.Arrays;class Solut…

PDF扫描档智能方向识别:多模型投票机制的实践测试 救活古典书籍

2025-02-22 20:10物联全栈123 尊敬的诸位&#xff01;我是一名物联网工程师。关注我&#xff0c;持续分享最新物联网与AI资讯和开发实战。期望与您携手探寻物联网与 AI 的无尽可能 RAG知识库搭建的过程中&#xff0c;扫描档pdf的支持和准确率一直是个大家都不愿主动提起的事情…

轻松搭建:使用Anaconda创建虚拟环境并在PyCharm中配置

一、使用Anaconda创建虚拟环境 1. 安装Anaconda 2..conda常用的命令 3. 创建虚拟环境-以搭建MachineVision为例 4. 激活虚拟环境 5. 安装依赖包 二、PyCharm配置环境 在进行Python项目开发时&#xff0c;合理的环境管理是必不可少的&#xff0c;特别是当你在多个项目中…

驱动开发系列39 - Linux Graphics 3D 绘制流程(二)- 设置渲染管线

一:概述 Intel 的 Iris 驱动是 Mesa 中的 Gallium 驱动,主要用于 Intel Gen8+ GPU(Broadwell 及更新架构)。它负责与 i915 内核 DRM 驱动交互,并通过 Vulkan(ANV)、OpenGL(Iris Gallium)、或 OpenCL(Clover)来提供 3D 加速。在 Iris 驱动中,GPU Pipeline 设置 涉及…