1.异步场景分析
在开发系统的过程中,通常会考虑到系统的性能问题,提升系统性能的一个重要思想就是"串行" 改 "并行"。说起"并行"自然离不开"异步",今天我们就来聊聊如何使用Spring的@Async的异步注解。
1.2 Spring 业务的异步实现
1.2.1 启动异步配置
基于注解方式的配置中,借助@EnableAsync注解进行异步启动声明,SpringBoot版的项目中,将此注解应用到启动类上,代码示例如下:
@EnableAsync //Spring容器启动时会创建线程池
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
1.2.2 Spring中@Async注解应用
在需要异步执行的业务方法上,使用@Async方法进行异步声明。
/**
* @Async描述的方法为一个异步切入点方法,
* 此方法会在spring提供的线程池中的线程上去运行
*/
@Async
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void insert(Log log) {
System.out.println(Thread.currentThread().getName()+"-->LogServiceImpl.insert");
try{Thread.sleep(5000);}catch (Exception e){}//模拟耗时操作
System.out.println(log);
logMapper.insert(log);
}
当我们需要自己对Spring框架提供的线程池进行一些简易配置,可以参考如下代码:
spring:
task:
execution:
pool:
queue-capacity: 128
core-size: 5
max-size: 128
keep-alive: 60000
thread-name-prefix: db-service-task-
其中:
-
core-size : 核心线程数,当池中线程数没达到core-size的值时,每接收一个新的任务都会创建一个新线程,然后存储到池。假如池中线程数已经达到core-size设置的值,再接收新的任务时,要检测是否有空闲的核心线程,假如有,则使用空闲的核心线程执行新的任务。
-
queue-capacity : 队列容量,假如核心线程数已达到core-size设置的值,并且所有的核心线程都在忙,再来新的任务,会将任务存储到任务队列。
-
max-size : 最大线程数,当任务队列已满,核心线程也都在忙,再来新的任务则会创建新的线程,但所有线程数不能超过max-size设置的值,否则可能会出现异常(拒绝执行)
-
keep-alive : 线程空闲时间,假如池中的线程数多余core-size设置的值,此时又没有新的任务,则一旦空闲线程空闲时间超过keep-alive设置的时间值,则会被释放。
-
thread-name-prefix : 线程名的前缀,项目中设置线程名的目的主要是为了对线程进行识别,一旦出现线程问题,可以更好的定位问题。对于Spring框架中线程池配置参数的涵义,可以参考ThreadPoolExecutor对象中的解释。
1.2.3 Spring 自定义异步池的实现
对于@Async注解默认会基于 ThreadPoolTaskExecutor 对象获取工作线程,然后调用由 @Async 描述的方法,让方法运行于另一个工作线程,以实现异步操作。
但是假如系统中的默认拒绝处理策略以及任务执行过程的异常处理不能满足自身业务需求的话 , 可以对异步线程池进行自定义操作 , 线程池优化设计如下 :
@Slf4j
@Setter
@Configuration //此注解描述的类为一个配置类,需要交给Spring管理
//读取Spring配置文件中以spring.async.task为前缀的数据,并通过set方法注入给属性
@ConfigurationProperties("spring.async.task")
public class SpringAsyncConfig implements AsyncConfigurer {
private int corePoolSize = 5;
private int maxPoolSize = 100;
private int keepAliveSeconds = 60;
private int queueCapacity= 128;
private String threadNamePrefix = "tast===>";
@Override
public Executor getAsyncExecutor() {
System.out.println("corePoolSize="+corePoolSize);
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(maxPoolSize);
executor.setKeepAliveSeconds(keepAliveSeconds);
executor.setQueueCapacity(queueCapacity);
executor.setThreadNamePrefix(threadNamePrefix);
//自定义拒绝处理策略
executor.setRejectedExecutionHandler(new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
log.error("队列已满并且已无线程可用");
}
});
executor.initialize();
return executor;
}
//当执行有异常时
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new AsyncUncaughtExceptionHandler() {
@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
log.error("任务执行时出现了 {}",ex.getMessage());
}
};
}
}
application.yml 配置文件中连接池的自定义配置如下 :
spring:
async:
task:
corePoolSize: 10
maxPoolSize: 20
keepAliveSeconds: 50
queueCapacity: 128
threadNamePrefix: db-async-service-task-
后续在业务类中,假如我们使用@Async注解描述业务方法,默认会使用ThreadPoolTaskExecutor池对象中的线程执行异步任务。