当前位置: 首页 > news >正文

异步方法和多线程有什么区别,他们的实现逻辑是什么以及为什么异步方法: 不能和调用者在同一个类中

一、异步方法 vs 多线程:本质区别

维度多线程(Multithreading)异步(Asynchronous)
本质资源模型:操作系统提供的并发执行单元编程范式:一种“非阻塞调用”的设计思想
目的并行执行任务,提高 CPU 利用率不阻塞调用者,提高 I/O 吞吐和响应性
关系异步可以用多线程实现多线程不一定用于异步(如 join() 就是同步)
类比马路(物理基础设施)交通规则(如何高效通行)

一句话总结

  • 多线程是“车”(执行载体)。
  • 异步是“导航”(告诉调用者:“你不用等,我好了通知你”)。
  • 你可以用多线程实现异步,也可以用事件循环(Node.js)、协程(Kotlin)实现异步。

二、实现逻辑:从底层到框架

1. 多线程实现逻辑(JVM 层面)

Thread thread = new Thread(() -> {System.out.println("我在新线程执行: " + Thread.currentThread().getName());
});
thread.start(); // JVM 调用 pthread_create() 创建 OS 线程

2. 异步方法实现逻辑(Spring @Async)

Spring 的 @Async 是基于 AOP(面向切面编程) + 动态代理 + 线程池 实现的。

核心组件:
  • @EnableAsync:启用异步支持,注册 AOP 切面。
  • AsyncAnnotationAdvisor:AOP 切面,匹配 @Async 方法。
  • AsyncExecutionInterceptor:拦截器,负责提交任务到线程池。
  • TaskExecutor:线程池,执行异步任务。

三、为什么“异步方法不能和调用者在同一个类中”?

1. 根本原因:Spring AOP 代理机制失效

Spring 的 @Async 依赖 AOP 代理。当你调用一个 @Async 方法时,真正执行的是代理对象,而不是原始对象

代理机制图解:
+---------------------+
|   UserService (原始)  |
|   + saveUser()       |
+----------↑-----------+| 被代理
+----------↓-----------+
| UserService$$Proxy  |
|   + saveUser()       | ← @Async 拦截逻辑在这里
+---------------------+
❌ 问题:同类调用(Self-Invocation)
@Service
public class UserService {public void register(User user) {saveUser(user);        // ❌ 直接调用本类方法sendWelcomeEmail();    // ❌ 绕过代理!}@Asyncpublic void sendWelcomeEmail() {System.out.println("发送邮件: " + Thread.currentThread().getName());}
}
为什么失效?
  • register() 调用 sendWelcomeEmail() 时,使用的是 this.sendWelcomeEmail()
  • this 是原始对象,不是代理对象。
  • 因此,@Async 的拦截逻辑完全被绕过,方法在同一个线程中同步执行。

🔥 结果:你以为是异步,其实是同步!性能瓶颈、阻塞调用者。


✅ 正确做法:通过代理对象调用

@Service
public class UserService {@Autowiredprivate UserService self; // 自我注入 → 注入的是代理对象public void register(User user) {saveUser(user);self.sendWelcomeEmail(); // ✅ 通过代理调用}@Asyncpublic void sendWelcomeEmail() {System.out.println("发送邮件: " + Thread.currentThread().getName());}
}

self 是 Spring 注入的代理对象,调用 self.sendWelcomeEmail() 会触发 AOP 拦截。


四、解决方案对比(6 种)

方案代码示例优点缺点
1. 自我注入@Autowired private Service self;简洁,无需上下文依赖 Spring 循环引用
2. ApplicationContextctx.getBean(Service.class)不依赖循环引用代码稍冗长
3. AopContextAopContext.currentProxy()直接获取代理需 exposeProxy=true,不推荐
4. 提取到新类@Service AsyncWorker{}架构清晰,解耦增加类数量
5. 使用 CompletableFutureCompletableFuture.runAsync()无需 AOP,灵活不统一
6. 使用事件机制ApplicationEventPublisher完全解耦,事件驱动异步延迟可能高

🏆 推荐顺序

  1. 提取到新类(最佳架构)
  2. 自我注入(快速修复)
  3. CompletableFuture(灵活控制)

五、CompletableFuture:绕过 AOP 限制的现代方案

@Service
public class UserService {@Autowiredprivate EmailService emailService;private final Executor asyncExecutor = Executors.newFixedThreadPool(5); // 或注入 @Qualifier("asyncExecutor")public void register(User user) {saveUser(user);// 使用 CompletableFuture 实现异步,无需 @AsyncCompletableFuture.runAsync(() -> emailService.sendWelcome(user), asyncExecutor).exceptionally(ex -> {log.error("邮件发送失败", ex);return null;});System.out.println("注册完成,邮件异步发送中...");}
}

优势

  • 不依赖 AOP 代理
  • 可组合、可链式调用
  • 精确控制线程池
  • 支持异常处理

六、企业级最佳实践

1. 自定义线程池(避免默认线程池)

@Configuration
@EnableAsync
public class AsyncConfig {@Bean("businessExecutor")public Executor businessExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(10);executor.setMaxPoolSize(20);executor.setQueueCapacity(100);executor.setThreadNamePrefix("Biz-");executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());executor.initialize();return executor;}
}

使用:

@Async("businessExecutor")
public void asyncMethod() { ... }

2. 异步方法必须是 public

@Async
private void method() { } // ❌ 无效!代理无法拦截

✅ 必须是 public 方法。


3. 异常处理

@Async
public CompletableFuture<String> riskyTask() {try {// 可能出错return CompletableFuture.completedFuture("success");} catch (Exception e) {return CompletableFuture.failedFuture(e);}
}// 或使用 exceptionally
future.exceptionally(ex -> {log.error("任务失败", ex);return "fallback";
});

七、总结:

🎯 最终结论

  1. 异步是目标(不阻塞)多线程是手段之一
  2. @Async 不能同类调用,是因为 AOP 代理机制要求通过代理对象调用
  3. 解决方案:自我注入、提取服务、CompletableFuture
  4. 生产环境推荐提取异步逻辑到独立服务类 + 自定义线程池
http://www.dtcms.com/a/355930.html

相关文章:

  • VisionPro联合编程控件导入WinFrom以及VS卡死问题
  • GCC版本和C语言标准版本的对应关系
  • 一个Demo射击小计(纯蓝图)
  • 前端学习 10-1 :验证中的UVM
  • .Net Core Web 架构(管道机制)的底层实现
  • jadx反向编译JAR包
  • 基于SQL数据库的智能问答系统设计与实现
  • Codeforces Round 1043 (Div. 3) D. From 1 to Infinity
  • 2025年9月计算机二级C++语言程序设计——选择题打卡Day9
  • 【数据分享】珠江三角洲水系地理空间全套数据集
  • x64dbg的基本调试操作 (未完,待补充)
  • 通信协议再升级,PROFINET和EtherNet IP网关迎接改造升级大挑战
  • 智慧清洁革新者:有鹿机器人自述
  • @Jenkins 介绍、部署与使用标准作业程序
  • 深入 OpenHarmony 内核:设备待机管理模块的休眠调度与资源节能技术
  • AT_abc407_f [ABC407F] Sums of Sliding Window Maximum
  • 告别低效!三坐标测量机提高油缸导向套检测效率
  • 拷贝构造和赋值重载有什么区别
  • 转发、重定向
  • 什么是强化学习? ——— 帮助新手了解
  • 基于51单片机的远程wifi浇花系统设计
  • Snagit 2025.3.0 截图贴图录像编辑
  • Android Keystore签名文件详解与安全防护
  • shell编程学习
  • 基于深度学习的档案级图像修复:Coderformer AI技术解析与应用实践
  • 一、晶振与布局布线处理
  • Python Imaging Library (PIL) 全面指南:Python Imaging Library (PIL)基础图像处理入门
  • 呼叫中心录音加密与数据隔离技术方案全解析
  • Wagtail 扩展 HomePage 模型(一个简单的 例子)
  • 人工智能-python-深度学习-过拟合与欠拟合:概念、判断与解决方法