在应用开发中,经常都有用到在后台跑定时任务的需求。比如需要在服务后台跑一个定时任务来进行数据清理、数据定时增量同步、定时发送邮件、爬虫定时抓取等等,这种情况下,我们往往需要执行定时任务。在java中定时任务有多种实现方式,比如使用线程、使用Timer、使用ScheduledExecutorService、Spring Task等等。本文会简单讲述一下上述几种方式的实现方法。
1. 使用普通线程Thread
创建一个thread,然后让它在while循环里一直运行着,通过sleep方法来达到定时任务的效果。这样可以快速简单的实现,代码如下:
public class Task1 {
public static void main(String[] args) {
// run in a second
final long timeInterval = 1000;
Runnable runnable = new Runnable() {
public void run() {
while (true) {
// ------- code for task to run
System.out.println("Hello !!");
// ------- ends here
try {
Thread.sleep(timeInterval);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread thread = new Thread(runnable);
thread.start();
}
}
可以实现间隔一定时间执行任务的需求,但是功能比较单一,不能定时。这种方式比较简单明了,示例代码中就单独展示了。
2. Timer方式
Timer是JDK自带的java.util.Timer包中的工具类,通过调度java.util.TimerTask的方式让程序再某个时间按照某一个频度执行,一般用的较少。Timer调度任务的方法有两种,一个事schedule方法,另一个是scheduleAtFixedRate方法。先看一下Timer类中两个方法的重载方法:
S.N. | 方法及说明 |
---|---|
1. | void schedule(TimerTask task, long delay) 将task延迟delay毫秒后执行一次 |
2. | void schedule(TimerTask task, Date time) 将task在time启动执行一次 |
3. | void schedule(TimerTask task, long delay, long period) 将task延迟delay毫秒后启动,之后每隔period毫秒执行一次 |
4. | void schedule(TimerTask task, Date firstTime, long period) 将task在firstTime启动,之后每隔period毫秒执行一次,如果firstTime小于当前时间,则立即执行任务,如果某次任务执行时间大于period,本次任务执行结束,立即执行下一次任务,之后每个任务的周期还是period,整个过程不存在追任务的行为。 |
5. | void scheduleAtFixedRate(TimerTask task, long delay, long period) 将task延迟delay毫秒后启动,之后每隔period毫秒执行一次,使用方法和3一致 |
6. | void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) 将task在firstTime启动,之后每隔period毫秒执行一次,与方法4不同的是,如果firstTime小于当前时间,则立即执行任务,之后会存在追任务的过程,比如period为5s,基本上每个任务执行时间是3s,那剩下来的两秒不再等待,直接用来执行任务,知道某个时间点,达到预期任务执行的次数。 |
2.1 schedule task demo
@Component
public class ScheduleTask {
@PostConstruct
public void startScheduleTask(){
Timer timer = new Timer();
//耗时2s左右的任务
TimerTask task1 = new TimerTask() {
@Override
public void run() {
//获得该任务该次执行的期望开始时间
System.out.println("expect start time: " + new Date(scheduledExecutionTime()));
System.out.println("task1 start: " + new Date());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
System.out.println("task1 end: " + new Date());
}
};
LocalDateTime localDateTime1 = LocalDateTime.now().plusSeconds(-11);
Date date = Date.from(localDateTime1.atZone(ZoneId.systemDefault()).toInstant());
System.out.println("now: " + new Date());
System.out.println("timer start: " + date);
//设置调度的启动时间为11s前
timer.schedule(task1, date, 5000);
}
}
设置任务启动时间为当前时间前的11S,每个任务执行时间为2S。
2.2 schedule task执行过程
now: Wed Sep 05 20:54:10 CST 2018
#设置任务启动时间为11S前
timer start: Wed Sep 05 20:53:59 CST 2018
#首次任务期望执行时间为当前,并不是11S前
expect start time: Wed Sep 05 20:54:10 CST 2018
task1 start: Wed Sep 05 20:54:10 CST 2018
task1 end: Wed Sep 05 20:54:12 CST 2018
#每次任务执行间隔为5S
expect start time: Wed Sep 05 20:54:15 CST 2018
task1 start: Wed Sep 05 20:54:15 CST 2018
task1 end: Wed Sep 05 20:54:17 CST 2018
expect start time: Wed Sep 05 20:54:20 CST 2018
task1 start: Wed Sep 05 20:54:20 CST 2018
task1 end: Wed Sep 05 20:54:22 CST 2018
2.3 scheduleAtFixedRate task demo
@Component
public class scheduleAtFixedRateTask {
@PostConstruct
public void startScheduleAtFixedRateTask() {
Timer timer = new Timer();
//耗时2s左右的任务
TimerTask task1 = new TimerTask() {
@Override
public void run() {
//获得该任务该次执行的期望开始时间
System.out.println("expect run time: " + new Date(scheduledExecutionTime()));
System.out.println("task1 start: " + new Date());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
System.out.println("task1 end: " + new Date());
}
};
LocalDateTime localDateTime1 = LocalDateTime.now().plusSeconds(-11);
Date date = Date.from(localDateTime1.atZone(ZoneId.systemDefault()).toInstant());
System.out.println("now: " + new Date());
System.out.println("timer start: " + date);
//设置调度的启动时间为11s前
timer.scheduleAtFixedRate(task1, date, 5000);
}
}
设置任务启动时间为当前时间前的11S,每个任务执行时间为2S。
2.4 scheduleAtFixedRate task执行过程
now: Wed Sep 05 20:59:24 CST 2018
#设置任务启动时间为11S前
timer start: Wed Sep 05 20:59:13 CST 2018
#首次任务期望启动时间为11S前
expect run time: Wed Sep 05 20:59:13 CST 2018
task1 start: Wed Sep 05 20:59:24 CST 2018
task1 end: Wed Sep 05 20:59:26 CST 2018
expect run time: Wed Sep 05 20:59:18 CST 2018
#上次任务执行结束并没有接着等3秒,直接进行下一次任务
task1 start: Wed Sep 05 20:59:26 CST 2018
task1 end: Wed Sep 05 20:59:28 CST 2018
expect run time: Wed Sep 05 20:59:23 CST 2018
task1 start: Wed Sep 05 20:59:28 CST 2018
task1 end: Wed Sep 05 20:59:30 CST 2018
expect run time: Wed Sep 05 20:59:28 CST 2018
task1 start: Wed Sep 05 20:59:30 CST 2018
task1 end: Wed Sep 05 20:59:32 CST 2018
#追赶上
expect run time: Wed Sep 05 20:59:33 CST 2018
task1 start: Wed Sep 05 20:59:33 CST 2018
task1 end: Wed Sep 05 20:59:35 CST 2018
expect run time: Wed Sep 05 20:59:38 CST 2018
task1 start: Wed Sep 05 20:59:38 CST 2018
task1 end: Wed Sep 05 20:59:40 CST 2018
注意:在示例代码中,为task类添加了@Component注解,在spring启动时,会进行Bean注册,执行@PostConstruct方法,实现了任务排期。为了展现效果,可以在测试相应task的时候,把其他的task的@Component注解注释掉。
3. ScheduledThreadPoolExecutor
Timer存在一些缺陷,比如Timer运行多个TimeTask时,只要其中有一个因任务报错没有捕获抛出的异常,其它任务便会自动终止运行。jdk5之后出现了一种新的定时器工具ScheduledThreadPoolExecutor,ScheduledThreadPoolExecutor继承自ThreadPoolExecutor,但是ScheduledThreadPoolExecutor几个构造方法中只能设置corePoolSize。它作为一个使用corePoolSize线程和一个无界队列的固定大小的线程池,调整maximumPoolSize没有效果。一个线程负责一个schedulexxx方法的执行,corePoolSize小于执行的schedulexxx方法个数时,放入任务队列。
S.N. | 方法及说明 |
---|---|
1. | ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) 延迟delay个unit单位执行一次command,并返回一个future,该future的get只会是null |
2. | <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) 延迟delay个unit单位执行一次callable,并返回一个future,future可以拿到callable的结果 |
3. | ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) 延迟delay个unit单位启动command,之后每period个unit单位执行一次command |
4. | ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) 延迟delay个unit单位启动command,每个任务之间的间隔为delay个unit单位 |
3.1 ScheduleWithFixedDelayTask demo
@Component
public class ScheduleWithFixedDelayTask {
@PostConstruct
public void starkTask(){
TimerTask task1 = new TimerTask() {
@Override
public void run() {
//获得该任务该次执行的期望开始时间
System.out.println("expect start time: " + new Date(scheduledExecutionTime()));
System.out.println("task1 start: " + new Date());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
System.out.println("task1 end: " + new Date());
}
};
System.out.println("now: " + new Date());
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(2);
executor.scheduleWithFixedDelay(task1, 0, 1000, TimeUnit.MILLISECONDS);
}
}
3.2 ScheduleWithFixedDelayTask执行过程
now: Wed Sep 05 21:51:36 CST 2018
expect start time: Thu Jan 01 08:00:00 CST 1970
task1 start: Wed Sep 05 21:51:36 CST 2018
task1 end: Wed Sep 05 21:51:38 CST 2018
expect start time: Thu Jan 01 08:00:00 CST 1970
#下一次任务开始时间间隔了1S
task1 start: Wed Sep 05 21:51:39 CST 2018
task1 end: Wed Sep 05 21:51:41 CST 2018
expect start time: Thu Jan 01 08:00:00 CST 1970
task1 start: Wed Sep 05 21:51:42 CST 2018
task1 end: Wed Sep 05 21:51:44 CST 2018
expect start time: Thu Jan 01 08:00:00 CST 1970
task1 start: Wed Sep 05 21:51:45 CST 2018
task1 end: Wed Sep 05 21:51:47 CST 2018
另外可以发现,在ScheduleWithFixedDelayTask中,期望开始时间是不生效的。
3.3 ScheduleAtFixedRateTask demo
@Component
public class ScheduleAtFixedRateTask {
@PostConstruct
public void starkTask(){
TimerTask task2 = new TimerTask() {
@Override
public void run() {
//获得该任务该次执行的期望开始时间
System.out.println("expect start time: " + new Date(scheduledExecutionTime()));
System.out.println("task2 start: " + new Date());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
System.out.println("task2 end: " + new Date());
}
};
System.out.println("now: " + new Date());
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(2);
executor.scheduleAtFixedRate(task2, 0, 1000, TimeUnit.MILLISECONDS);
}
}
3.2 ScheduleAtFixedRateTask执行过程
now: Wed Sep 05 21:54:37 CST 2018
expect start time: Thu Jan 01 08:00:00 CST 1970
task2 start: Wed Sep 05 21:54:37 CST 2018
task2 end: Wed Sep 05 21:54:39 CST 2018
expect start time: Thu Jan 01 08:00:00 CST 1970
task2 start: Wed Sep 05 21:54:39 CST 2018
task2 end: Wed Sep 05 21:54:41 CST 2018
expect start time: Thu Jan 01 08:00:00 CST 1970
task2 start: Wed Sep 05 21:54:41 CST 2018
task2 end: Wed Sep 05 21:54:43 CST 2018
expect start time: Thu Jan 01 08:00:00 CST 1970
task2 start: Wed Sep 05 21:54:43 CST 2018
task2 end: Wed Sep 05 21:54:45 CST 2018
4. Spring Task
4.1 任务定义
@Component
@Slf4j
public class SpringTask {
@Async
@Scheduled(cron = "0/1 * * * * *")
public void scheduled1() throws InterruptedException {
log.info("scheduled1 每1秒执行一次:{}", LocalDateTime.now());
Thread.sleep(3000);
}
@Scheduled(fixedRate = 1000)
public void scheduled2() throws InterruptedException {
log.info("scheduled2 每1秒执行一次:{}", LocalDateTime.now());
Thread.sleep(3000);
}
@Scheduled(fixedDelay = 3000)
public void scheduled3() throws InterruptedException {
log.info("scheduled3 上次执行完毕后隔3秒继续执行:{}", LocalDateTime.now());
Thread.sleep(5000);
}
}
这里讲解一下各个注解的含义:
@Scheduled表名方法为定时任务
- cron:cron表达式,根据表达式循环执行,@Scheduled(cron = “0/5 * * * * *”)表示任务将在5、10、15、20…这种情况下进行工作,cron表达式可在线生成
- fixedRate:每隔多久执行一次,@Scheduled(fixedRate = 1000) 假设第一次工作时间为2018-09-06 10:30:01,工作时长为3秒,那么下次任务的时候就是2018-09-06 10:30:04,配置成异步后,只要到了执行时间就会开辟新的线程工作;如果@Scheduled(fixedRate = 3000) 假设第一次工作时间为2018-09-06 10:30:01,工作时长为1秒,那么下次任务的时间依然是2018-09-06 10:30:04
- fixedDelay:当前任务执行完毕后等待多久继续下次任务,@Scheduled(fixedDelay = 3000) 假设第一次任务工作时间为2018-09-06 10:30:01,工作时长为5秒,那么下次任务的时间就是2018-09-06 10:30:09
- initialDelay:第一次执行延迟时间,只是做延迟的设定
@Async代表该任务可以进行异步工作,由原本的串行改为并行
4.2 开启线程池
@SpringBootApplication
@EnableScheduling
@EnableAsync
public class SpringTaskApplicationContext {
public static void main(String[] args) {
SpringApplication.run(SpringTaskApplicationContext.class, args);
}
/**
*默认情况下 TaskScheduler 的 poolSize = 1 多个任务的情况下,如果第一个任务没执行完毕,后续的任务将会进入等待状态
* @return 线程池
*/
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(10);
return taskScheduler;
}
}
- @EnableScheduling: 表示开启对@Scheduled注解的解析
- @EnableAsync: 注解表示开启@Async注解的解析,将串行化的任务给并行化。比如@Scheduled(cron = “0/1 * * * * *”)第一次工作时间为2018-09-06 10:30:01,工作周期为3秒;如果不加@Async那么下一次工作时间就是2018-09-06 10:30:04。如果加了@Async下一次工作时间就是2018-09-06 10:30:02
4.3 执行结果
scheduled2 每1秒执行一次:2018-09-06T10:23:54.510
scheduled3 上次执行完毕后隔3秒继续执行:2018-09-06T10:23:54.510
#scheduled1开启异步,每秒执行一次
scheduled1 每1秒执行一次:2018-09-06T10:23:55.014
scheduled1 每1秒执行一次:2018-09-06T10:23:56.002
scheduled1 每1秒执行一次:2018-09-06T10:23:57.001
#Schedule2定义每秒执行一次,但是任务执行时间为3S,没开启异步,所以每隔3S执行一次
scheduled2 每1秒执行一次:2018-09-06T10:23:57.513
scheduled1 每1秒执行一次:2018-09-06T10:23:58.002
scheduled1 每1秒执行一次:2018-09-06T10:23:59.002
scheduled1 每1秒执行一次:2018-09-06T10:24:00.001
scheduled2 每1秒执行一次:2018-09-06T10:24:00.513
scheduled1 每1秒执行一次:2018-09-06T10:24:01
scheduled1 每1秒执行一次:2018-09-06T10:24:02.002
#scheduled3 任务执行时间5S,任务之间间隔3S,总共8S
scheduled3 上次执行完毕后隔3秒继续执行:2018-09-06T10:24:02.513
scheduled1 每1秒执行一次:2018-09-06T10:24:03.001
scheduled2 每1秒执行一次:2018-09-06T10:24:03.513
…………
示例代码:码云 – 卓立 – 定时任务
参考链接: