- @Async
- 1.@Async是什么
- 2.@Async的使用
- 步骤一:在启动类上面加@EnableAsync注解
- 步骤二:在对应的方法上加@Async注解
- 3.@Async的使用原理
- 问题1:当不指定的时候,默认线程池是什么?具体配置是什么?看源码
- 问题2:为什么被@Async注解的方法只能返回 void 或者 Future 类型(如AsyncResult类),如果返回了其他类型则为null
- 4.课后作业:什么情况下会默认使用SimpleAsyncTaskExecutor线程池?
Spring自带了支持任务调度和异步方法调用的注解,在需要被调用的方法上加上@Async注解,则该方法会被异步调用
2.@Async的使用 步骤一:在启动类上面加@EnableAsync注解@EnableAsync
@SpringBootApplication
public class OperationApplication {
public static void main(String[] args) {
SpringApplication.run(OperationApplication.class, args);
}
}
或者
创建一个配置类,在配置类上面加@EnableAsync注解
@Configuration
@EnableAsync
public class ThreadPoolConfig {
}
注意:@Async默认启动的线程池,属性
步骤二:在对应的方法上加@Async注解 @Async
public void method() {
log.info("执行该线程的名字为:" + Thread.currentThread().getName());
}
3.@Async的使用原理
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@documented
public @interface Async {
String value() default "";
}
剖析注释可以发现
@Async 注解中有一个 value 属性,看注释应该是可以指定是哪个线程池的
写一个demo
相关的依赖
org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-web org.projectlombok lombok org.springframework.boot spring-boot-starter-test test
启动类
@EnableAsync // 开启异步注解
@SpringBootApplication
public class ThreadDemoApplication {
public static void main(String[] args) {
SpringApplication.run(ThreadDemoApplication.class, args);
}
}
调用的方法
@Slf4j
@Service
public class AsyncService {
@Async
public void method() {
log.info("执行该线程的名字为:" + Thread.currentThread().getName());
}
}
写一个调用异步方法的接口
@Slf4j
@RestController
public class TestController {
@Resource
private AsyncService asyncService;
@GetMapping("/test")
public void test() {
log.info("执行test方法的线程的名字为:" + Thread.currentThread().getName());
asyncService.method();
}
}
调用结果
INFO 22864 --- [ task-1] c.e.threaddemo.service.AsyncService : 执行该线程的名字为:task-1
为了一探默认线程池的配置,就行黑盒压测一下
@Slf4j
@RestController
public class TestController {
@Resource
private AsyncService asyncService;
@GetMapping("/test")
public void test(@RequestParam("num") Integer num) {
log.info("执行test方法的线程的名字为:" + Thread.currentThread().getName());
for (int i = 0; i < num; i++) {
asyncService.method();
}
}
}
不多废话,直接进行100000次异步调用
http://localhost:8080/test?num=100000
发现控制台并未报错,单堆内存几乎要爆炸,怀疑可能是一个无界队列,任务有可能都进队列里面排队了,导致内存飙升
默认的线程池有导致内存溢出的风险。
可以通过创建自定义线程池代替默认线程池
@Configuration
@EnableAsync
public class ThreadPoolConfig {
@Bean("taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(10);
taskExecutor.setMaxPoolSize(50);
taskExecutor.setQueueCapacity(200);
taskExecutor.setKeepAliveSeconds(60);
taskExecutor.setThreadNamePrefix("Thread-");
taskExecutor.setAwaitTerminationSeconds(60);
return taskExecutor;
}
}
控制台
2021-12-24 18:18:38.014 INFO 10688 --- [ Thread-30] c.e.threaddemo.service.AsyncService : 执行该线程的名字为:Thread-30 2021-12-24 18:18:38.014 INFO 10688 --- [ Thread-2] c.e.threaddemo.service.AsyncService : 执行该线程的名字为:Thread-2 2021-12-24 18:18:38.014 INFO 10688 --- [ Thread-8] c.e.threaddemo.service.AsyncService : 执行该线程的名字为:Thread-8 2021-12-24 18:18:38.014 INFO 10688 --- [ Thread-15] c.e.threaddemo.service.AsyncService : 执行该线程的名字为:Thread-15 2021-12-24 18:18:38.014 INFO 10688 --- [ Thread-25] c.e.threaddemo.service.AsyncService : 执行该线程的名字为:Thread-25 2021-12-24 18:18:38.014 INFO 10688 --- [ Thread-5] c.e.threaddemo.service.AsyncService : 执行该线程的名字为:Thread-5 2021-12-24 18:18:38.014 INFO 10688 --- [ Thread-31] c.e.threaddemo.service.AsyncService : 执行该线程的名字为:Thread-31 2021-12-24 18:18:38.014 INFO 10688 --- [ Thread-24] c.e.threaddemo.service.AsyncService : 执行该线程的名字为:Thread-24 2021-12-24 18:18:38.014 INFO 10688 --- [ Thread-22] c.e.threaddemo.service.AsyncService : 执行该线程的名字为:Thread-22 2021-12-24 18:18:38.014 INFO 10688 --- [ Thread-9] c.e.threaddemo.service.AsyncService : 执行该线程的名字为:Thread-9 2021-12-24 18:18:38.022 ERROR 10688 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.core.task.TaskRejectedException: Executor [java.util.concurrent.ThreadPoolExecutor@44a62e23[Running, pool size = 50, active threads = 50, queued tasks = 91, completed tasks = 1001]] did not accept task: org.springframework.aop.interceptor.AsyncExecutionInterceptor$$Lambda$580/2138858786@265a267e] with root cause java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@293459fe rejected from java.util.concurrent.ThreadPoolExecutor@44a62e23[Running, pool size = 50, active threads = 50, queued tasks = 129, completed tasks = 961] at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063) ~[na:1.8.0_162] at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830) ~[na:1.8.0_162] at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379) ~[na:1.8.0_162] at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:134) ~[na:1.8.0_162]
说明 @Async使用了我们自定义的线程池,当超出最大线程数且队列满的时候,通过默认拒绝策略new ThreadPoolExecutor.AbortPolicy()丢弃任务并抛出RejectedExecutionException异常
问题1:当不指定的时候,默认线程池是什么?具体配置是什么?看源码value属性被使用的地方就一个,走
F8断点往下走,进入Aspect
其中defaultExecutor就是我们需要知道的默认线程池
private SingletonSupplierdefaultExecutor;
SingletonSupplier是一个函数式编程,不懂可以百度,方便了解一下源码的运行
在这里成功获取到了线程池,让我们看看线程池的参数
得到@Async使用的线程池Bean名称为applicationTaskExecutor,显而易见这个队列设置为Integer.MAX_VALUE,所以在上面会导致堆内存溢出
线程池applicationTaskExecutor的属性都是根据TaskExecutionProperties中的Pool来的,以下筛选了部分TaskExecutionProperties的源码:
@ConfigurationProperties("spring.task.execution")
public class TaskExecutionProperties {
private final Pool pool = new Pool();
private String threadNamePrefix = "task-";
public Pool getPool() {
return this.pool;
}
public static class Pool {
private int queueCapacity = Integer.MAX_VALUE;
private int coreSize = 8;
private int maxSize = Integer.MAX_VALUE;
private boolean allowCoreThreadTimeout = true;
private Duration keepAlive = Duration.ofSeconds(60);
}
}
问题2:为什么被@Async注解的方法只能返回 void 或者 Future 类型(如AsyncResult类),如果返回了其他类型则为null
- 现象
- 剖析源码
查看异步任务拦截器AsyncExecutionInterceptor的invoke方法
public class AsyncExecutionInterceptor extends AsyncExecutionAspectSupport implements MethodInterceptor, Ordered {
@Override
@Nullable
public Object invoke(final MethodInvocation invocation) throws Throwable {
Class> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
Method specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);
final Method userDeclaredMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
// 1.从map里面拿到当前method对应的线程池对象
AsyncTaskExecutor executor = determineAsyncExecutor(userDeclaredMethod);
if (executor == null) {
throw new IllegalStateException(
"No executor specified and no default executor set on AsyncExecutionInterceptor either");
}
// 2.获取执行的对象
Callable
doSubmit方法判断当returnType不是Future类型的时候会执行summit方法后直接返回一个null



