工作中经常会用到定时任务,有的用linuxcrontab来实现系统级别的定时调用,当然这种只能调用脚本,不能在我们的程序中实现高度灵活的配置。定时任务的实现有很多,我之前也做过一些笔记,因为在分布式应用中,定时需要小心处理,否则会很容易地出现数据错乱,因此出现了很多适用于分布式场景定时器。当然分布式不在本文讨论范围,这里只想聊聊简单的单机应用,而且是最简单的Spring Task

一、基本使用

使用起来十分简单,在ssm的工程中不需要额外引入其他的依赖即可使用。因为已经在Spring-Context中集成。

image

第一步是配置文件中开启定时任务的注解:

1
2
<!--引入spring task定时任务-->
<task:annotation-driven/>

在头部引入相应的DTD约束文件:

1
2
3
4
xmlns:task="http://www.springframework.org/schema/task"

http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-3.0.xsd

配置方面就结束了。

下面写一个定时任务吧:

1
2
3
4
5
6
7
8
9
10
11
@Component
public class Task {

/**
* 每隔一分钟从数据库读取一下
*/
@Scheduled(cron = "0 0/1 * * * ? ")
public void getIntervalFromDatabase() {
System.out.println("一分钟执行一次~");
}
}

对于这里的cron表达式,相信大家都知道了,按照一定的匹配规则即可实现比较复杂的定时场景。当然,可以使用可视化的页面来配置:http://cron.qqe2.com/

二、另一种实现:Timer

在这个最简单的应用中,就是实现每隔几分钟来做一些事情的简单场景,还可以使用JDK自带的Timer来实现。下面给出一个最简单的使用:

1
2
3
4
5
6
7
8
9
10
11
12
Timer timer = new Timer();
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
// scheduledExecutionTime() 返回此任务最近开始执行的时间
Date date = new Date(this.scheduledExecutionTime());
System.out.println("timeTask run " + date);
}
};

// 从现在开始每间隔 1000 ms 计划执行一个任务
timer.schedule(timerTask, 0, 1000);

Timer 可以按计划执行重复的任务或者定时执行指定任务,这是因为 Timer 内部利用了一个后台线程 TimerThread 有计划地执行指定任务。

  • Timer:是一个实用工具类,该类用来调度一个线程(schedule a thread),使它可以在将来某一时刻执行。 JavaTimer 类可以调度一个任务运行一次或定期循环运行。 Timer tasks should complete quickly. 即定时器中的操作要尽可能花费短的时间。
  • TimerTask:一个抽象类,它实现了 Runnable 接口。我们需要扩展该类以便创建自己的 TimerTask ,这个 TimerTask 可以被 Timer 调度。

内部的实现原理还是有点意思的,后面有时间来扒一扒它的实现原理。既然Timer这么简单为什么我不用呢?当然了,在这里我觉得Spring Task更简单。

三、Timer存在的问题

有一个显著问题是:Timer在执行定时任务时只会创建一个线程,所以如果存在多个任务(task1和task2),且任务时间过长,超过了两个任务的间隔时间,那么就不再那么准时了。因为只有一个线程,线程需要排队,前面一个线程未及时执行完毕,势必会影响后续的任务执行。

第二个问题是:如果TimerTask抛出RuntimeExceptionTimer会停止所有任务的运行。

如果不引入Spring如何解决上述问题呢?这个时候ScheduledExecutorService闪亮登场,利用线程池来调度任务,不会出现一个任务延迟导致第二个任务无法准时执行的问题,并且在ScheduledExecutorService调度两个任务的时候,其中一个任务抛出异常不影响第二个任务的正常执行。

具体的对比可以参见文章Java 并发专题 : Timer的缺陷 用ScheduledExecutorService替代

因此,尽量避免使用Timer要成为我们的共识啦。优秀的那么多,何必用这个呢?

当然,还有开源的定时器可以使用,功能更加强大,整合也不难。比如quartz和为分布式而生的Elastic-Job