在应用开发中,经常都有用到在后台跑定时任务的需求。比如需要在服务后台跑一个定时任务来进行数据清理、数据定时增量同步、定时发送邮件、爬虫定时抓取等等,这种情况下,我们往往需要执行定时任务。在java中定时任务有多种实现方式,比如使用线程、使用Timer、使用ScheduledExecutorService、Spring Task等等。本文会简单讲述一下上述几种方式的实现方法。
创建一个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();
}
}
可以实现间隔一定时间执行任务的需求,但是功能比较单一,不能定时。这种方式比较简单明了,示例代码中就单独展示了。
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,那剩下来的两秒不再等待,直接用来执行任务,知道某个时间点,达到预期任务执行的次数。 |
@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。
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
@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。
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注解注释掉。
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. | 延迟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单位 |
@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);
}
}
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中,期望开始时间是不生效的。
@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);
}
}
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
@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表名方法为定时任务
@Async代表该任务可以进行异步工作,由原本的串行改为并行
@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;
}
}
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
…………
示例代码:码云 – 卓立 – 定时任务
参考链接:
- Java实现定时任务的三种方法
- java定时工具的辟谣
- 详解java定时任务