补习系列(9)-springboot 定时器,你用对了吗
简介
大多数的应用程序都离不开定时器,通常在程序启动时、运行期间会需要执行一些特殊的处理任务。
比如资源初始化、数据统计等等,SpringBoot 作为一个灵活的框架,有许多方式可以实现定时器或异步任务。
我总结了下,大致有以下几种:
- 使用 JDK 的 TimerTask
- 使用 JDK 自带调度线程池
- 使用 Quartz 调度框架
- 使用 @Scheduled 、@Async 注解
其中第一种使用 TimerTask 的方法已经不建议使用,原因是在系统时间跳变时TimerTask存在挂死的风险。
第三种使用 Quartz 调度框架可以实现非常强大的定时器功能,包括分布式调度定时器等等。
考虑作为大多数场景使用的方式,下面的篇幅将主要介绍 第二、第四种。
一、应用启动任务
在 SpringBoot 应用程序启动时,可以通过以下两个接口实现初始化任务:
- CommandLineRunner
- ApplicationRunner
两者的区别不大,唯一的不同在于:
CommandLineRunner 接收一组字符串形式的进程命令启动参数;
ApplicationRunner 接收一个经过解析封装的参数体对象。
详细的对比看下代码:
public class CommandLines {
private static final Logger logger = LoggerFactory.getLogger(CommandLines.class);
@Component
@Order(1)
public static class CommandLineAppStartupRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
logger.info(
"[CommandLineRunner]Application started with command-line arguments: {} .To kill this application, press Ctrl + C.",
Arrays.toString(args));
}
}
@Component
@Order(2)
public static class AppStartupRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
logger.info("[ApplicationRunner]Your application started with option names : {}", args.getOptionNames());
}
}
}
二、JDK 自带调度线程池
为了实现定时调度,需要用到 ScheduledThreadpoolExecutor
初始化一个线程池的代码如下:
/**
* 构造调度线程池
*
* @param corePoolSize
* @param poolName
* @return
*/
public static ScheduledThreadPoolExecutor newSchedulingPool(int corePoolSize, String poolName) {
ScheduledThreadPoolExecutor threadPoolExecutor = new ScheduledThreadPoolExecutor(corePoolSize);
// 设置变量
if (!StringUtils.isEmpty(poolName)) {
threadPoolExecutor.setThreadFactory(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread tr = new Thread(r, poolName + r.hashCode());
return tr;
}
});
}
return threadPoolExecutor;
}
可以将 corePoolSize 指定为大于1,以实现定时任务的并发执行。
为了在 SpringBoot 项目中使用,我们利用一个CommandLineRunner来实现:
@Component
@Order(1)
public class ExecutorTimer implements CommandLineRunner {
private static final Logger logger = LoggerFactory.getLogger(ExecutorTimer.class);
private ScheduledExecutorService schedulePool;
@Override
public void run(String... args) throws Exception {
logger.info("start executor tasks");
schedulePool = ThreadPools.newSchedulingPool(2);
schedulePool.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
logger.info("run on every minute");
}
}, 5, 60, TimeUnit.SECONDS);
}
}
schedulePool.scheduleWithFixedDelay 指定了调度任务以固定的频率执行。
三、@Scheduled
@Scheduled 是 Spring3.0 提供的一种基于注解实现调度任务的方式。
在使用之前,需要通过 @EnableScheduling 注解启用该功能。
代码如下:
/**
* 利用@Scheduled注解实现定时器
*
* @author atp
*
*/
@Component
public class ScheduleTimer {
private static final Logger logger = LoggerFactory.getLogger(ScheduleTimer.class);
/**
* 每10s
*/
@Scheduled(initialDelay = 5000, fixedDelay = 10000)
public void onFixDelay() {
logger.info("schedule job on every 10 seconds");
}
/**
* 每分钟的0秒执行
*/
@Scheduled(cron = "0 * * * * *")
public void onCron() {
logger.info("schedule job on every minute(0 second)");
}
/**
* 启用定时器配置
*
* @author atp
*
*/
@Configuration
@EnableScheduling
public static class ScheduleConfig {
}
}
说明
上述代码中展示了两种定时器的使用方式:
第一种方式
指定初始延迟(initialDelay)、固定延迟(fixedDelay);
第二种方式
通过 cron 表达式定义
这与 unix/linux 系统 crontab 的定义类似,可以实现非常灵活的定制。
一些 cron 表达式的样例:
| 表达式 | 说明 |
|---|---|
| 0 0 * * * * | 每天的第一个小时 |
| */10 * * * * * | 每10秒钟 |
| 0 0 8-10 * * * | 每天的8,9,10点钟整点 |
| 0 * 6,19 * * * | 每天的6点和19点每分钟 |
| 0 0/30 8-10 * * * | 每天8:00, 8:30, 9:00, 9:30 10:00 |
| 0 0 9-17 * * MON-FRI | 工作日的9点到17点 |
| 0 0 0 25 12 ? | 每年的圣诞夜午夜 |
定制 @Scheduled 线程池
默认情况下,@Scheduled 注解的任务是由一个单线程的线程池进行调度的。
这样会导致应用内的定时任务只能串行执行。
为了实现定时任务并发,或是更细致的定制,
可以使用 SchedulingConfigurer 接口。
代码如下:
@Configuration
@EnableScheduling
public class ScheduleConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskExecutor());
}
@Bean(destroyMethod="shutdown")
public Executor taskExecutor() {
//线程池大小
return Executors.newScheduledThreadPool(50);
}
}
四、@Async
@Async 注解的意义在于将 Bean方法的执行方式改为异步方式。
比如 在前端请求处理时,能通过异步执行提前返回结果。
类似的,该注解需要配合 @EnableAsync 注解使用。
代码如下:
@Configuration
@EnableAsync
public static class ScheduleConfig {
}
使用 @Async 实现模拟任务
@Component
public class AsyncTimer implements CommandLineRunner {
private static final Logger logger = LoggerFactory.getLogger(AsyncTimer.class);
@Autowired
private AsyncTask task;
@Override
public void run(String... args) throws Exception {
long t1 = System.currentTimeMillis();
task.doAsyncWork();
long t2 = System.currentTimeMillis();
logger.info("async timer execute in {} ms", t2 - t1);
}
@Component
public static class AsyncTask {
private static final Logger logger = LoggerFactory.getLogger(AsyncTask.class);
@Async
public void doAsyncWork() {
long t1 = System.currentTimeMillis();
try {
Thread.sleep((long) (Math.random() * 5000));
} catch (InterruptedException e) {
}
long t2 = System.currentTimeMillis();
logger.info("async task execute in {} ms", t2 - t1);
}
}
示例代码中,AsyncTask 等待一段随机时间后结束。
而 AsyncTimer 执行了 task.doAsyncWork,将提前返回。
执行结果如下:
- async timer execute in 2 ms
- async task execute in 3154 ms
这里需要注意一点,异步的实现,其实是通过 Spring 的 AOP 能力实现的。
对于 AsyncTask 内部方法间的调用却无法达到效果。
定制 @Async 线程池
对于 @Async 线程池的定制需使用 AsyncConfigurer接口。
代码如下:
@Configuration
@EnableAsync
public static class ScheduleConfig implements AsyncConfigurer {
@Bean
public ThreadPoolTaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
//线程池大小
scheduler.setPoolSize(60);
scheduler.setThreadNamePrefix("AsyncTask-");
scheduler.setAwaitTerminationSeconds(60);
scheduler.setWaitForTasksToCompleteOnShutdown(true);
return scheduler;
}
@Override
public Executor getAsyncExecutor() {
return taskScheduler();
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return null;
}
}
小结
定时异步任务是应用程序通用的诉求,本文收集了几种常见的实现方法。
作为 SpringBoot 应用来说,使用注解是最为便捷的。
在这里我们对 @Scheduled、@Async 几个常用的注解进行了说明,
并提供定制其线程池的方法,希望对读者能有一定帮助。
欢迎继续关注"美码师的补习系列-springboot篇" ,如果觉得老司机的文章还不赖,请多多分享转发-
补习系列(9)-springboot 定时器,你用对了吗的更多相关文章
- 补习系列(15)-springboot 分布式会话原理
目录 一.背景 二.SpringBoot 分布式会话 三.样例程序 四.原理进阶 A. 序列化 B. 会话代理 C. 数据老化 小结 一.背景 在 补习系列(3)-springboot 几种scope ...
- 补习系列(14)-springboot redis 整合-数据读写
目录 一.简介 二.SpringBoot Redis 读写 A. 引入 spring-data-redis B. 序列化 C. 读写样例 三.方法级缓存 四.连接池 小结 一.简介 在 补习系列(A3 ...
- 补习系列(13)-springboot redis 与发布订阅
目录 一.订阅发布 常见应用 二.Redis 与订阅发布 三.SpringBoot 与订阅发布 A. 消息模型 B. 序列化 C. 发布消息 D. 接收消息 小结 一.订阅发布 订阅发布是一种常见的设 ...
- 补习系列(19)-springboot JPA + PostGreSQL
目录 SpringBoot 整合 PostGreSQL 一.PostGreSQL简介 二.关于 SpringDataJPA 三.整合 PostGreSQL A. 依赖包 B. 配置文件 C. 模型定义 ...
- 补习系列(18)-springboot H2 迷你数据库
目录 关于 H2 一.H2 用作本地数据库 1. 引入依赖: 2. 配置文件 3. 样例数据 二.H2 用于单元测试 1. 依赖包 2. 测试配置 3. 测试代码 小结 关于 H2 H2 数据库是一个 ...
- 补习系列(16)-springboot mongodb 数据库应用技巧
目录 一.关于 MongoDB 二.Spring-Data-Mongo 三.整合 MongoDB CRUD A. 引入框架 B. 数据库配置 C. 数据模型 D. 数据操作 E. 自定义操作 四.高级 ...
- 补习系列(1)-springboot项目基础搭建课
目录 前言 一.基础结构 二.添加代码 三.应用配置 四.日志配置 五.打包部署 小结 前言 springboot 最近火的不行,目前几乎已经是 spring 家族最耀眼的项目了.抛开微服务.技术社区 ...
- 补习系列(17)-springboot mongodb 内嵌数据库
目录 简介 一.使用 flapdoodle.embed.mongo A. 引入依赖 B. 准备测试类 C. 完善配置 D. 启动测试 细节 二.使用Fongo A. 引入框架 B. 准备测试类 C.业 ...
- 补习系列(12)-springboot 与邮件发送
目录 一.邮件协议 关于数据传输 二.SpringBoot 与邮件 A. 添加依赖 B. 配置文件 C. 发送文本邮件 D.发送附件 E. 发送Html邮件 三.CID与图片 参考文档 一.邮件协议 ...
随机推荐
- pip install –r ./requirements.txt 报错 改成 pip install -r requirements.txt 成功
Invalid requirement: '–r'Traceback (most recent call last): File "/home/dev/.pyenv/versions/3.6 ...
- Y1S002 xshell脚本编写示意
SecureCRT可以自己录制脚本,非常的方便:但是考虑到CRT收费,所以不计划把CRT作为使用的终端. vbs脚本(test.vbs): Sub main xsh.Screen.Synchronou ...
- [SCOI2015]国旗计划
Description: A 国正在开展一项伟大的计划 -- 国旗计划.这项计划的内容是边防战士手举国旗环绕边境线奔袭一圈.这项计划需要多名边防战士以接力的形式共同完成,为此,国土安全局已经挑选了 \ ...
- 匿名函数 javascript
匿名函数: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF ...
- appium定位
一.链接基本信息 二.在appium界面中 三,定位 三.通过ui automator viewer抓取手机页面元素,点击红框按钮会抓取当前手机界面app全部元素;路径在sdk>tools下面的 ...
- DevExpress内 GridControl中复选框值问题
在DevExpress的 GridControl内的复选柜勾选后,界面看到是勾选状态,但对应的DataView的值仍未变,在以下事件内处理 在对应的DataView内的 CellValueChangi ...
- ssh 连接失败 sz rz 安装
sz 下载命令, rz上传命令的安装 sudo apt-get install lrzsz 1. 检查sshd服务的状态以及端口是否正常, 如下为正常状态 sudo netstat -nlp | gr ...
- mysql学习3
1.索引 索引是表的目录,在查找内容之前可以先在目录中查找索引位置,以此快速定位查询数据.对于索引, 会保存在额外的文件中. 作用: 约束 加速查找 1.1.建立索引 a.额外的文件保存特殊的数据结构 ...
- codeforces 13 b
给你三根线段判段是否组成了A 条件,两条线段交于端点并且夹角不大于90,第三条线段端点在两条线段上并且划分的大小满足 大:小<4:1 注释很全.(主要是我记不清楚了,,好像过了一个多星期了) # ...
- vue组件里定时器销毁问题
我在a页面写一个定时,让他每秒钟打印一个1,然后跳转到b页面,此时可以看到,定时器依然在执行.这样是非常消耗性能的.如下图所示: 解决方法1: 首先我在data函数里面进行定义定时器名称: data( ...