前言:
SpringBoot 的异步多线程需要从 java 的多线程基础说起,可以参考 java 多线程实现的三种方式区别。SpringBoot 在此基础上进行了多次封装,所以使用起来非常方便。
一、核心参数说明
ThreadPoolExecutor 是 java 的核心线程池类;Spring 对 ThreadPoolExecutor 进行二次封装形成了 ThreadPoolTaskExecutor,其中几个核心参数除了名字略有改动,核心含义没变,下面说明一下:
- corePoolSize:核心线程池数
- maxPoolSize:最大线程池数
- queueCapacity:线程池队列最大容量
- keepAliveSeconds:允许线程的空闲时间,核心线程外的线程在空闲时间到达后会被销毁
- threadNamePrefix:线程池名的前缀
- rejectedExecutionHandler:拒绝策略
其中拒绝策略是线程达到某种饱和后的线程池的操作策略,总共四种:
- AbortPolicy:如果线程池队列满了丢掉任务并且抛出RejectedExecutionException异常
- DiscardPolicy:如果线程池队列满了,会直接丢掉这个任务并且不会抛出异常
- DiscardOldestPolicy:如果队列满了,会将最早进入队列的任务删掉,再尝试加入队列
- CallerRunsPolicy:如果添加到线程池失败,那么主线程会自己去执行该任务
说明一下几个核心参数和拒绝策略是怎么工作的:
1.核心线程数:m,最大线程数: n(一般 n > m),线程池队列最大容量: j,线程总数: s;
2.线程进场后(s<m),线程会直接启动直到启动的线程数达到核心线程数(s=m);
3.线程继续进场(m<s<m+j),线程开始排队,此时启动的线程数不会增加直到队列饱和(s=m+j);
4.线程继续进场(m+j<s<n+j),每进场一个线程,该线程就会启动;直到启动的线程达到最大线程数(s=n+j);
5.线程继续进场(s>n+j),此时触发拒绝策略;
二、使用说明
首先配置线程池:
@Configuration
@EnableAsync
public class ThreadPoolConfig {
@Bean("normalThreadPool") //线程池实例名,多个线程池配置需要声明,一个线程池可有可无
public Executor executorNormal() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(3);
executor.setMaxPoolSize(5);
executor.setQueueCapacity(3);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("NORMAL--");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
executor.initialize();
return executor;
}
}
其次在需要异步的方法上加 @Async 注解:
@Slf4j
@Service
public class ThreadTaskService {
@Async("normalThreadPool") //多个线程池配置时需指定配置实例
public void task() {
log.info("task start...");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("task end...");
}
}
异步的调用跟普通 service 方法没区别:
@Slf4j
@RestController
@RequestMapping("/thread")
public class ThreadTaskController {
@Autowired
ThreadTaskService taskService;
@GetMapping(value = "/start")
public String getValue() {
taskService.task();
return "hello...";
}
}
三、带返回值的异步
要获取异步函数的返回值可以使用 Future,但是Future 的get方法是阻塞的,使用时需要注意。
@Async("normalThreadPool")
public CompletableFuture<String> task() {
String result = "000";
log.info("task start...");
try {
Thread.sleep(5000);
result = "333";
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("task end...");
return CompletableFuture.completedFuture(result);
}
@Slf4j
@RestController
@RequestMapping("/thread")
public class ThreadTaskController {
@Autowired
ThreadTaskService taskService;
@GetMapping(value = "/start")
public String getValue() throws ExecutionException, InterruptedException {
CompletableFuture<String> result = taskService.task();
log.info("result:{}", result.get()); // get 方法会使主线程阻塞
return "hello...";
}
}
四、几种异步失败的情况
1. 异步方法使用static关键词修饰;
2. 缺少 @EnableAsync
注解;
3. 同一个类中,一个方法调用另外一个有@Async注解的方法(原因是@Async注解的方法,是在代理类中执行的);