@Async 是 Spring 3.0 提供的一个注解,用于标识某类(下的公共方法)或某方法会执行异步调用。

接下来,我们来看下 @Async 的基本使用和实现原理。

1.基本使用

@Async 基本使用可以分为以下 3 步:

  1. 项目中开启异步支持
  2. 创建异步方法
  3. 调用异步方法

1.1 开启异步支持

以 Spring Boot 项目为例,我们首先需要在 Spring Boot 的启动类,也就是带有@SpringBootApplication 注解的类上添加 @EnableAsync 注解,以开启异步方法执行的支持,如下代码所示:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync; @SpringBootApplication
@EnableAsync
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}

1.2 创建异步方法

创建异步方法是在需要异步执行的方法上添加 @Async 注解,这个方法一定是要放在被 IoC 容器管理的 Bean 中,只有被 IoC 管理的类才能实现异步调用,例如在带有 @Service 注解的类中创建异步方法:

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service; @Service
public class AsyncService { @Async
public void performAsyncTask() {
// 这里放置需要异步执行的代码
System.out.println("异步任务正在执行,当前线程:" + Thread.currentThread().getName());
}
}

1.3 调用异步方法

在其他类或方法中,通过注入这个服务类的实例来调用异步方法。注意,直接在同一个类内部调用不会触发异步行为,必须通过注入的实例调用,使用 new 创建的对象也不能进行异步方法调用,具体实现代码如下:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController; @RestController
public class MyController { @Autowired
private AsyncService asyncService; @GetMapping("/startAsync")
public String startAsyncTask() {
asyncService.performAsyncTask();
return "异步任务已启动";
}
}

2.实现原理

简单来说,@Async 注解是由 AOP(面向切面)实现的,具体来说,它是由 AsyncAnnotationAdvisor 这个切面类来实现的。

在 AsyncAnnotationAdvisor 中,会使用 AsyncExecutionInterceptor 来处理 @Async 注解,它会在被 @Async 注解标识的方法被调用时,创建一个异步代理对象来执行方法。这个异步代理对象会在一个新的线程中调用被 @Async 注解标识的方法,从而实现方法的异步执行。

在 AsyncExecutionInterceptor 中,核心方法是 getDefaultExecutor 方法,使用此方法来获取一个线程池来执行被 @Async 注解修饰的方法,它的实现源码如下:

@Nullable
protected Executor getDefaultExecutor(@Nullable BeanFactory beanFactory) {
Executor defaultExecutor = super.getDefaultExecutor(beanFactory);
return (Executor)(defaultExecutor != null ? defaultExecutor : new SimpleAsyncTaskExecutor());
}

此方法实现比较简单,它是先尝试调用父类 AsyncExecutionAspectSupport#getDefaultExecutor 方法获取线程池,如果父类方法获取不到线程池再用创建 SimpleAsyncTaskExecutor 对象作为 Async 的线程池返回。

而 SimpleAsyncTaskExecutor 中在执行任务时是这样的:

protected void doExecute(Runnable task) {
this.newThread(task).start();
}

可以看出,在 Spring 框架中如果使用默认的 @Async 注解,它的执行比较简单粗暴,并没有使用线程池,而是每次创建线程来执行,所以在 Spring 框架中是不能直接使用 @Async 注解的,需要使用 @Async 注解搭配自定义的线程池,既实现 AsyncConfigurer 接口来提供自定义的 ThreadPoolTaskExecutor 来创建线程池,以确保 @Async 能真正的使用线程池来执行异步任务。

然而,在 Spring Boot 中,因为在框架启动时,自动注入了 ThreadPoolTaskExecutor,如下源码所示:

@ConditionalOnClass({ThreadPoolTaskExecutor.class})
@AutoConfiguration
@EnableConfigurationProperties({TaskExecutionProperties.class})
@Import({TaskExecutorConfigurations.ThreadPoolTaskExecutorBuilderConfiguration.class, TaskExecutorConfigurations.TaskExecutorBuilderConfiguration.class, TaskExecutorConfigurations.SimpleAsyncTaskExecutorBuilderConfiguration.class, TaskExecutorConfigurations.TaskExecutorConfiguration.class})
public class TaskExecutionAutoConfiguration {
public static final String APPLICATION_TASK_EXECUTOR_BEAN_NAME = "applicationTaskExecutor"; public TaskExecutionAutoConfiguration() {
}
}

具体的构建细节源码如下:

@Bean
@ConditionalOnMissingBean({TaskExecutorBuilder.class, ThreadPoolTaskExecutorBuilder.class})
ThreadPoolTaskExecutorBuilder threadPoolTaskExecutorBuilder(TaskExecutionProperties properties, ObjectProvider<ThreadPoolTaskExecutorCustomizer> threadPoolTaskExecutorCustomizers, ObjectProvider<TaskExecutorCustomizer> taskExecutorCustomizers, ObjectProvider<TaskDecorator> taskDecorator) {
TaskExecutionProperties.Pool pool = properties.getPool();
ThreadPoolTaskExecutorBuilder builder = new ThreadPoolTaskExecutorBuilder();
builder = builder.queueCapacity(pool.getQueueCapacity());
builder = builder.corePoolSize(pool.getCoreSize());
builder = builder.maxPoolSize(pool.getMaxSize());
builder = builder.allowCoreThreadTimeOut(pool.isAllowCoreThreadTimeout());
builder = builder.keepAlive(pool.getKeepAlive());
TaskExecutionProperties.Shutdown shutdown = properties.getShutdown();
builder = builder.awaitTermination(shutdown.isAwaitTermination());
builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod());
builder = builder.threadNamePrefix(properties.getThreadNamePrefix());
Stream var10001 = threadPoolTaskExecutorCustomizers.orderedStream();
Objects.requireNonNull(var10001);
builder = builder.customizers(var10001::iterator);
builder = builder.taskDecorator((TaskDecorator)taskDecorator.getIfUnique());
builder = builder.additionalCustomizers(taskExecutorCustomizers.orderedStream().map(this::adapt).toList());
return builder;
}

因此在 Spring Boot 框架中可以直接使用 @Async 注解,无需担心它每次都会创建线程来执行的问题

课后思考

为什么使用 @Async 注解不能解决循环依赖的问题?为什么使用 @Async 注解会导致事务实现?

本文已收录到我的面试小站 www.javacn.site,其中包含的内容有:Redis、JVM、并发、并发、MySQL、Spring、Spring MVC、Spring Boot、Spring Cloud、MyBatis、设计模式、消息队列等模块。

阿里面试:说说@Async实现原理?的更多相关文章

  1. zookeeper 负载均衡 核心机制-实现原理 包含ZAB协议(滴滴,阿里面试)

    面试也经常问kafka的原理,以及zookeeper与kafka原理的区别:kafka 数据一致性-leader,follower机制与zookeeper的区别: zookeeper是如何实现负载均衡 ...

  2. 红黑树之 原理和算法详细介绍(阿里面试-treemap使用了红黑树) 红黑树的时间复杂度是O(lgn) 高度<=2log(n+1)1、X节点左旋-将X右边的子节点变成 父节点 2、X节点右旋-将X左边的子节点变成父节点

    红黑树插入删除 具体参考:红黑树原理以及插入.删除算法 附图例说明   (阿里的高德一直追着问) 或者插入的情况参考:红黑树原理以及插入.删除算法 附图例说明 红黑树与AVL树 红黑树 的时间复杂度 ...

  3. 阿里面试官:Android中binder机制的实现原理及过程?

    Binder 是 Android 系统中非常重要的组成部分.Android 系统中的许多功能建立在 Binder 机制之上.在这篇文章中,我们会对 Android 中的 Binder 在系统架构中的作 ...

  4. 阿里面试回来,想和Java程序员谈一谈(转载)

    引言 其实本来真的没打算写这篇文章,主要是LZ得记忆力不是很好,不像一些记忆力强的人,面试完以后,几乎能把自己和面试官的对话都给记下来.LZ自己当初面试完以后,除了记住一些聊过的知识点以外,具体的内容 ...

  5. 【阿里面试系列】Java线程的应用及挑战

    文章简介 上一篇文章[「阿里面试系列」搞懂并发编程,轻松应对80%的面试场景]我们了解了进程和线程的发展历史.线程的生命周期.线程的优势和使用场景,这一篇,我们从Java层面更进一步了解线程的使用.关 ...

  6. Java程序员从阿里面试回来,这些面试题你们会吗?

    前不久刚从阿里面试回来,为了这场面试可以说准备了一个半月,做的准备就是刷题和看视频看书充实自己的技术,话说是真难啊,不过还算顺利拿到了offer,有很多面试题我已经记不起来了,这些是当天回家整理好的, ...

  7. 阿里面试回来,想和Java程序员谈一谈

    引言 其实本来真的没打算写这篇文章,主要是LZ得记忆力不是很好,不像一些记忆力强的人,面试完以后,几乎能把自己和面试官的对话都给记下来.LZ自己当初面试完以后,除了记住一些聊过的知识点以外,具体的内容 ...

  8. Spring学习10- bean的生命周期(阿里面试题目两次面试均提到)

    找工作的时候有些人会被问道Spring中Bean的生命周期,其实也就是考察一下对Spring是否熟悉,工作中很少用到其中的内容,那我们简单看一下. 在说明前可以思考一下Servlet的生命周期:实例化 ...

  9. 一名十年Java程序员回忆阿里面试经历——揭开阿里面试的“遮羞布”

    阿里面试经历 去阿里面试可以说非常非常的偶然和戏剧性,因为本人根本没投简历,以至于阿里hr给我电话的时候我一度认为是诈骗电话.因为深圳这家公司不错我还想在这里干个两年左右再考虑考虑. 这个时候的本人已 ...

  10. 阿里面试Java程序员都问些什么?

    刚开始也是小白,也是一步步成成起来的.需要提的一点是,你将来是需要靠这个吃饭的,所以请对找工作保持十二分的热情,而且越早准备越好. 阿里一面 一面是在上午9点多接到支付宝的面试电话的,因为很期望能够尽 ...

随机推荐

  1. docker-compose搭建的Mysql主从复制

    设置前注意下面几点: 1)要保证同步服务期间之间的网络联通.即能相互ping通,能使用对方授权信息连接到对方数据库(防火墙开放3306端口). 2)关闭selinux. 3)同步前,双方数据库中需要同 ...

  2. docker 权限问题 Got permission denied while trying to connect to the Docker daemon socket at 。。。

    非root用户运行docker命令报如下错误 说明没有权限 haima@haima-PC:/usr/local/docker/docker_compose_efk$ docker ps -a Got ...

  3. C#中的对象深拷贝和浅拷贝

    目录 C#中的对象深拷贝和浅拷贝 概述 1. 浅拷贝 2. 深拷贝 总结 引用 C#中的对象深拷贝和浅拷贝 概述 在C#中,对象拷贝是指将一个对象的副本创建到另一个对象中.对象拷贝通常用于数据传输或创 ...

  4. 10分钟了解Flink SQL使用

    Flink 是一个流处理和批处理统一的大数据框架,专门为高吞吐量和低延迟而设计.开发者可以使用SQL进行流批统一处理,大大简化了数据处理的复杂性.本文将介绍Flink SQL的基本原理.使用方法.流批 ...

  5. 『手撕Vue-CLI』添加帮助和版本号

    前言 经过上一篇『手撕Vue-CLI』编码规范检查之后,手撕 Vue-CLI 已经进阶到了代码规范检查这一步,已经将基本的工程搭建好了,然后代码规范约束也已经加入了,并且将 nue-cli 指令绑定到 ...

  6. VS Code 代码片段编写教程

    VS Code 代码片段编写教程 最近要做一个vs code的代码片段插件,于是搜索和学习相关内容,整理分享给大家! [!TIP] 本篇博客50%+内容由BingChat提供,然后作者对内容进行验证和 ...

  7. 文件系统(四):FAT32文件系统实现原理

    FAT32是从FAT12.FAT16发展而来,目前主要应用在移动存储设备中,比如SD卡.TF卡.隐藏的FAT文件系统现在也有被大量使用在UEFI启动分区中. 为使文章简单易读,下面内容特意隐藏了很多实 ...

  8. Wpf UI框架 MaterialDesign 的使用记录

    近期公司有桌面客户端的开发需求,并且对样式和界面反馈有一定的要求,对比各种开源UI框架后确认使用MaterialDesign . 1.引入框架MaterialDesignThemes,注意下对应的版本 ...

  9. 解决老旧电脑在win7中浏览器访问https网站出现的Let‘sEncrypt证书过期的问题

    原因LetsEncrypt证书未过期,但是其顶级ca根证书 "DST Root CA X3"在2021-09-01过期了,老旧设备上的win系统会被影响到. 解决步骤下载三张Let ...

  10. AppFlow上新——智谱ChatGLM轻松接入聊天

    智谱AI 开放平台提供一系列具有不同功能和定价的大模型,包括通用大模型.超拟人大模型.图像大模型.向量大模型等,并且支持使用您的私有数据对模型进行微调.其中ChatGLM系列模型在国内也享有盛名,现在 ...