Spring 定时器和异步线程池 实践指南
前言:Spring:异步线程池和定时器 原理篇
一、Spring Scheduler
1. 创建一个 SpringBoot项目,在启动类上添加 @EnableScheduling 注解,表示开启定时任务。
2. 创建SchedulerService,在方法上面启用@Scheduled 注解
在方法上使用 @Scheduled 注解表示开启一个定时任务:下面参数单位都是毫秒
- fixedRate:表示按一定频率来执行定时任务,具体是指两次任务的开始时间间隔,即第二次任务开始时,第一次任务可能还没结束。
- fixedDelay:表示按一定时间间隔来执行定时任务,具体是指本次任务结束到下次任务开始之间的时间间隔。该属性还可以配合initialDelay使用, 定义该任务延迟执行时间。
- initialDelay:表示首次任务启动的延迟时间。与fixedDelay配合使用。
- cron:通过 cron 表达式来配置任务执行时间,cron 表达式格式为:[秒] [分] [小时] [日] [月] [周] [年]
package com.example.SchedulerDemo.service;import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.TimeUnit;@Service
public class SchedulerService {private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm:ss.SSS");@Scheduled(fixedRate = 2000)public void fixedRateTask() {System.out.println("[" + Thread.currentThread().getName() + "]" + " -> [fixedRateTask] run: " + LocalDateTime.now().format(formatter));}@Scheduled(fixedDelay = 2000)public void fixedDelayTask() {System.out.println("[" + Thread.currentThread().getName() + "]" + " -> [fixedDelayTask] run: " + LocalDateTime.now().format(formatter));}@Scheduled(initialDelay = 2000, fixedDelay = 2000)public void initialDelayTask() throws InterruptedException {System.out.println("[" + Thread.currentThread().getName() + "]" + " -> [fixedDelayTaskWithInitialDelay] run: " + LocalDateTime.now().format(formatter));TimeUnit.SECONDS.sleep(5);}@Scheduled(cron = "0/5 * * * * ?")public void cronTask() {System.out.println("[" + Thread.currentThread().getName() + "]" + " -> [cronTask] run: " + LocalDateTime.now().format(formatter));}
}
3. 运行Application,日志如下,可以看到由于单线程执行任务,后续的定时任务被block,等之前的任务执行完才排队执行。
二、 异步线程池——多线程执行任务
1. 创建AsyncThreadPoolConfig配置类,并启用@EnableAsync注解,表示开启异步事件的支持
package com.example.SchedulerDemo.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;@EnableAsync
@Configuration
public class AsyncThreadPoolConfig {@Bean// 设置为首选,以免被SimpleAsyncTaskExecutor替代@Primarypublic ThreadPoolTaskExecutor initialize() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();// 设置核心线程数(长期保持的最小线程数量,即使这些线程处于空闲状态也不会被回收)executor.setCorePoolSize(5);// 设置最大线程数executor.setMaxPoolSize(10);// 设置队列容量executor.setQueueCapacity(10);// 设置线程名称前缀executor.setThreadNamePrefix("MyThread-");// 用于控制线程池在关闭时是否等待正在执行的任务完成executor.setWaitForTasksToCompleteOnShutdown(true);// 初始化,必须executor.initialize();return executor;}}
2. 在定时任务的类或者方法上添加 @Async 注解。
package com.example.SchedulerDemo.service;import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.TimeUnit;@Service
public class SchedulerService {private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm:ss.SSS");@Async@Scheduled(fixedRate = 2000)public void fixedRateTask() {System.out.println("[" + Thread.currentThread().getName() + "]" + " -> [fixedRateTask] run: " + LocalDateTime.now().format(formatter));}@Async@Scheduled(fixedDelay = 2000)public void fixedDelayTask() {System.out.println("[" + Thread.currentThread().getName() + "]" + " -> [fixedDelayTask] run: " + LocalDateTime.now().format(formatter));}@Async@Scheduled(initialDelay = 2000, fixedDelay = 2000)public void initialDelayTask() throws InterruptedException {System.out.println("[" + Thread.currentThread().getName() + "]" + " -> [fixedDelayTaskWithInitialDelay] run: " + LocalDateTime.now().format(formatter));TimeUnit.SECONDS.sleep(5);}@Scheduled(cron = "0/5 * * * * ?")public void cronTask() {System.out.println("[" + Thread.currentThread().getName() + "]" + " -> [cronTask] run: " + LocalDateTime.now().format(formatter));}
}
3. 重新运行Application,日志如下,可以看到除了cornTask,其他三个任务配置到了异步线程池中,观察fixedRateTask,可以看到是间隔两秒的。