环境:
spring;spring boot
摘要说明:
项目开发过程中往往会出现需异步调用的情况,以便提高系统的响应速度或者提高部分业务的处理时间;
但异步调用需要根据系统的承受能力做好相关配置,而不是放任随意使用;
如系统批跑5000条数据做相关业务处理;若循环异步处理,则很可能将数据库给压垮;
所以这里需要控制系统异步线程池的大小及线程池的满额执行策略;
步骤:
1.@Asyns的使用
@Asyns的使用其实很简单,在系统已允许异步调用的情况下,在bean管理的类的方法上加上此注解就表示该方法可;
import java.util.Date;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
public class TestAsyncComponent {
@Async
// @Async("taskScheduler")
public void test() {
try {
System.out.println("测试异步!!!" + new Date());
} catch (Exception e) {
e.printStackTrace();
}
}
}
其中@Async表示使用默认线程池;指定线程池需注明线程池id;
2.传统spring项目(如ssm)下配置线程池
传统的spring项目下配置开启异步调用只需一个spring.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-4.0.xsd
">
<!-- 开启异步调用,缺省的executor给@Async使用 -->
<task:annotation-driven />
</beans>
指定自定义线程池如下配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-4.0.xsd
">
<!-- 给@Async指定线程池 -->
<task:annotation-driven executor="asyncExecutor" />
<!-- 线程池中最小线程数为20,最大数为150, 队列的capacity数为10,任务完成后,线程池中保留最小线程数20,超出的在10s内未使用将被结束掉,当使用的线程数超出线程池最大线程数且队列也已满时,由调用者所在线程来执行相应任务 -->
<task:executor id="asyncExecutor" pool-size="20-150"
queue-capacity="10" keep-alive="10" rejection-policy="CALLER_RUNS" />
</beans>
其中task中executor的相关配置如下:
- id:线程池id,要求唯一,可与@Async搭配使用
- pool-size:core size(最小线程数,缺省为1)-max size(最大线程池,缺省为Integer.MAX_VALUE)
- queue-capacity:当最小的线程数已经被占用满后,新的任务会被放进queue里面,当这个queue的capacity也被占满之后,pool里面会创建新线程处理这个任务,直到总线程数达到了max size。缺省值为:Integer.MAX_VALUE
- keep-alive:当系统创建的线程数多于core size时,若keep-alive(秒)没用调用则收回多于线程
- rejection-policy:当pool已经达到max size的时候,如何处理新任务:
ABORT(缺省):抛出TaskRejectedException异常,然后不执行
DISCARD:不执行,也不抛出异常
DISCARD_OLDEST:丢弃queue中最旧的那个任务
CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行即同步调用
实际线程池创建和销毁线程如下
- 如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
- 如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。
- 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maxPoolSize,建新的线程来处理被添加的任务。
- 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maxPoolSize,那么通过handler所指定的策略来处理此任务。也就是:处理任务的优先级为:核心线程corePoolSize、任务队列workQueue、最大线程 maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。
- 当线程池中的线程数量大于corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。
注:建议使用上述的CALLER_RUNS满额策略,此种策略就可以很好的解决摘要中的批量循环执行业务;当线程池满的时候自己线程同步执行,当线程池有空闲立刻又是异步执行
3.spring boot下配置线程池
spring boot下开启使用@Async可参考之前写过的一篇文章:https://blog.youkuaiyun.com/u010904188/article/details/82893473
这里主要说下spring boot下线程池的配置:
// 启动异步调用
@EnableAsync
@Configuration
class TaskPoolConfig {
// 核心线程数(setCorePoolSize)10:线程池创建时候初始化的线程数
// 最大线程数(setMaxPoolSize)20:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
// 缓冲队列(setQueueCapacity)200:用来缓冲执行任务的队列
// 允许线程的空闲时间(setKeepAliveSeconds)60秒:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
// 线程池名的前缀(setThreadNamePrefix):设置好了之后可以方便我们定位处理任务所在的线程池
// 线程池对拒绝任务的处理策略(setRejectedExecutionHandler):这里采用了CallerRunsPolicy策略,当线程池没有处理能力的时候,该策略会直接在 execute
// 方法的调用线程中运行被拒绝的任务(setWaitForTasksToCompleteOnShutdown);如果执行程序已关闭,则会丢弃该任务
// setWaitForTasksToCompleteOnShutdown(true)该方法就是这里的关键,用来设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean,这样这些异步任务的销毁就会先于异步线程池的销毁。
// 同时,这里还设置了setAwaitTerminationSeconds(60),该方法用来设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住。
@Bean("taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(200);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("taskExecutor-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
return executor;
}
}
在这里我们可以看到基本配置都是类似的;就多了个线程销毁机制和线程等待时间设置;