@[TOC]
1 配置自己的线程池
@Configuration
@EnableAsync
public class ThreadPoolConfig
{
// 核心线程池大小
private int corePoolSize = 50;
// 最大可创建的线程数
private int maxPoolSize = 200;
// 队列最大长度
private int queueCapacity = 1000;
// 线程池维护线程所允许的空闲时间
private int keepAliveSeconds = 300;
@Bean(name = "threadPoolTaskExecutor")
public ThreadPoolTaskExecutor threadPoolTaskExecutor()
{
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 设置最大线程池数量
executor.setMaxPoolSize(maxPoolSize);
// 核心线程数
executor.setCorePoolSize(corePoolSize);
// 线程池维护线程所允许的空闲时间
executor.setKeepAliveSeconds(keepAliveSeconds);
// 队列最大长度
executor.setQueueCapacity(queueCapacity);
// 线程前缀名称
executor.setThreadNamePrefix("ThreadPoolTaskExecutor======>");
// 设置线程池关闭时,等待所有任务都完成后,再继续销毁其他的bean
// 这样这些异步任务的销毁就会优先于 数据库连接池对象的销毁
// 也就是说当其他bean都完成任务销毁后,数据库连接池对象才会销毁
executor.setWaitForTasksToCompleteOnShutdown(true);
// 任务的等待时间,如果超过这个时间还没有销毁,就强制销毁,以确保应用最后能够被关闭,而不是阻塞住
executor.setAwaitTerminationSeconds(5);
// 线程池对拒绝任务(无线程可用)的处理策略
// 线程不够用时由调用的线程处理该任务
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
- 一定要自己配置,不要使用
SpringBoot
自带的,Spring
默认的线程池没有上限,并发多时,可能会OOM
。并且不是线程重用,每次都会新建一个线程。ThreadPoolTaskExecutor
会重用线程,节省创建线程的资源。因此,在使用ThreadLocal
时,也要及时清理缓存,否则,会获取到上一个任务的参数。2 使用
2.1 在Service层使用
Controller
层直接返回结果,具体的业务,由其他线程执行。@Override @Async("threadPoolTaskExecutor") public void concatAB() { ...... }
2.2 多线程中使用事务的写法
事务放到其他方法
@Transactional
,然后调用。@Override @Async("threadPoolTaskExecutor") public void concatAB() { addChange() ...... } @Transactional(rollbackFor = Exception.class,isolation = Isolation.SERIALIZABLE) public void addChange(){ ....... }
2.3 方法内多线程
2.3.1 错误写法
错误写法
@Override public void concatAB() { addChange() ...... } @Async("threadPoolTaskExecutor") public void addChange(){ ....... }
@Async注解可以让Spring使用线程池来异步执行带有该注解的方法,当一个带有@Async注解的方法被调用时,Spring会为其创建代理对象。通过这个代理对象来调用该方法,Spring就可以控制方法的异步执行了。
如果不是通过代理对象来调用@Async方法,方法的执行将与当前线程同步运行,不会实现异步处理的效果。所以在同一个类中调用加了@Async注解的方法,需要使用代理对象来调用以确保异步生效。
2.3.2 正确写法(一)
第一种,直接使用线程池执行异步方法。
@Autowired private ThreadPoolTaskExecutor threadPoolTaskExecutor; @Override public void concatAB() { threadPoolTaskExecutor.execute(() -> { .... }); }
2.3.2 正确写法(二)
使用代理对象
@Override public void concatAB() { // 获取代理对象 IDemoService demoService = (IDemoService) AopContext.currentProxy(); // 使用代理对象调用 demoService.addChange() ...... } @Async("threadPoolTaskExecutor") public void addChange(){ ....... }
2.3.3 正确写法(三)
DemoService中通过@Autowired将自身注入到selfProxy属性中,并使用selfProxy对象在syncMethod方法中调用了asyncMethod方法,从而实现了对同一类中的带有@Async注解的方法的异步调用。
@Autowired private DemoService selfProxy; @Async public void asyncMethod() { // 异步执行的逻辑 } public void syncMethod() { // 调用异步方法需要使用代理对象 selfProxy.asyncMethod(); }
3 线程池与并行流的选取
IO
密集型的业务,选取线程池来完成。(例如,连接数据库之类的操作)cpu
密集型的业务,选取并行流allelesvs.parallelStream()
的方式处理。(例如,计算类、过滤、求和、分组等)