注意事项

  1. 使用 @Async 注解将方法标记为异步方法
  2. 没有额外配置时,默认会采用 SimpleAsyncTaskExecutor 处理任务。每次调用@Async方法时,该执行器会创建一个新线程执行任务,而非复用线程。这就可能带来问题,要是并发请求数量很多,系统资源会被大量消耗,甚至有导致 OOM(内存溢出)的风险
  3. 可以通过配置ThreadPoolTaskExecutor来创建一个可复用线程的池,以此提升系统性能。
    • 默认线程池参数(未手动配置时):
      • 核心线程数(corePoolSize):8
      • 最大线程数(maxPoolSize):Integer.MAX_VALUE
      • 队列容量(queueCapacity):Integer.MAX_VALUE
      • 空闲线程保留时间:60秒
    • 可以在配置类或配置文件中修改默认线程池参数
  4. 异步方法必须在 public 方法中定义,并且不能在同一类中调用本类中的其他异步方法,否则其将会同步执行

配置线程池

配置类方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10); // 核心线程数
executor.setMaxPoolSize(50); // 最大线程数
executor.setQueueCapacity(100); // 队列容量
executor.setThreadNamePrefix("Async-");
executor.initialize();
return executor;
}
}

配置文件方式

Spring Boot 会读取这些配置并应用到自动创建的 ThreadPoolTaskExecutor 实例中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
spring:
task:
execution:
pool:
# 最大线程数
max-size: 16
# 核心线程数
core-size: 16
# 存活时间
keep-alive: 10s
# 队列大小
queue-capacity: 100
# 是否允许核心线程超时
allow-core-thread-timeout: true
# 线程名称前缀
thread-name-prefix: async-task-

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
@Component
@Slf4j
public class TaskFactory {

/**
* 模拟5秒的异步任务
*/
@Async
public Future<Boolean> asyncTask1() throws InterruptedException {
doTask("asyncTask1", 5);
return new AsyncResult<>(Boolean.TRUE);
}

/**
* 模拟2秒的异步任务
*/
@Async
public Future<Boolean> asyncTask2() throws InterruptedException {
doTask("asyncTask2", 2);
return new AsyncResult<>(Boolean.TRUE);
}

/**
* 模拟3秒的异步任务
*/
@Async
public Future<Boolean> asyncTask3() throws InterruptedException {
doTask("asyncTask3", 3);
return new AsyncResult<>(Boolean.TRUE);
}

/**
* 模拟5秒的同步任务
*/
public void task1() throws InterruptedException {
doTask("task1", 5);
}

/**
* 模拟2秒的同步任务
*/
public void task2() throws InterruptedException {
doTask("task2", 2);
}

/**
* 模拟3秒的同步任务
*/
public void task3() throws InterruptedException {
doTask("task3", 3);
}

private void doTask(String taskName, Integer time) throws InterruptedException {
log.info("{}开始执行,当前线程名称【{}】", taskName, Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(time);
log.info("{}执行成功,当前线程名称【{}】", taskName, Thread.currentThread().getName());
}
}