文章目录
任务调度是指基于给定时间点,给定时间间隔或者给定执行次数自动执行任务。
四种任务调度的 Java 实现:
- Timer
- ScheduledExecutor
- spring提供的定时任务
- 开源工具包 Quartz
开源工具包和Spring提供的定时任务实现在后面的章节介绍,本文主要介绍JDK中提供的定时任务的实现,此外,为结合实现复杂的任务调度,本文还将介绍 Calendar 的一些使用方法。
Timer
java.util.Timer 是最简单的一种实现任务调度的方法,下面给出一个具体的例子
package huyp.task.timer;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
/**
* 任务实现类
* */
public class TimerTest extends TimerTask{
private String jobName="";
private static String format = "yyyy-MM-dd HH:mm:ss";
static DateFormat dateFormat = new SimpleDateFormat(format);
public TimerTest(String jobName) {
super();
this.jobName=jobName;
}
@Override
public void run() {
System.out.println("now time"+dateFormat.format(new Date())+" execute "+jobName);
System.out.println("当前线程"+Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Timer timer=new Timer();
long delay1=1*1000;
long period1=1000;
System.out.println("now time"+dateFormat.format(new Date()));
//从现在开始1s之后,每隔1s执行一次job1
timer.schedule(new TimerTest("job1"), delay1,period1);
long delay2=2*1000;
long period2=2000;
//从现在开始2s之后,每隔2s执行一次job2
System.out.println("now time"+dateFormat.format(new Date()));
timer.schedule(new Timer2Test("job2"), delay2,period2);
}
}
package huyp.task.timer;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimerTask;
public class Timer2Test extends TimerTask{
private String jobName = "";
private String format = "yyyy-MM-dd HH:mm:ss";
DateFormat dateFormat = new SimpleDateFormat(format);
public Timer2Test(String jobName) {
super();
this.jobName=jobName;
}
@Override
public void run() {
System.out.println("now time"+dateFormat.format(new Date())+" execute "+jobName);
System.out.println("当前线程"+Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
设计核心
使用 Timer 实现任务调度的核心类是 Timer 和 TimerTask。其中 Timer 负责设定 TimerTask 的起始与间隔执行时间。使用者只需要创建一个 TimerTask 的继承类,实现自己的 run 方法,然后将其丢给 Timer 去执行即可。TimerTask是一个实现Runnable的类,run()方法中是具体的任务内容。
Timer类中有一个任务队列和一个任务线程
private final TaskQueue queue = new TaskQueue();
private final TimerThread thread = new TimerThread(queue);
TaskQueue任务队列实际上是一个TimerTask的数组
private TimerTask[] queue = new TimerTask[128];
构造函数
public Timer() {
this("Timer-" + serialNumber());
}
public Timer(boolean isDaemon) {
this("Timer-" + serialNumber(), isDaemon);
}
public Timer(String name) {
thread.setName(name);
thread.start();
}
public Timer(String name, boolean isDaemon) {
thread.setName(name);
thread.setDaemon(isDaemon);
thread.start();
}
从源代码中看出,当创建一个Timer时,实际上是启动了TaskThread线程,成为一个守护进程
//TaskThread实现了run方法,当线程调用start方法时就会调用这个方法
public void run() {
try {
mainLoop();
} finally {
// Someone killed this Thread, behave as if Timer cancelled
synchronized(queue) {
newTasksMayBeScheduled = false;
queue.clear(); // Eliminate obsolete references
}
}
}
//
private void mainLoop() {
while (true) {
try {
TimerTask task;
boolean taskFired;
synchronized(queue) {
// Wait for queue to become non-empty
while (queue.isEmpty() && newTasksMayBeScheduled)
//当任务队列为空时被阻塞,等待notify被唤起
queue.wait();
if (queue.isEmpty())
break; // Queue is empty and will forever remain; die
// Queue nonempty; look at first evt and do the right thing
long currentTime, executionTime;
task = queue.getMin();
//获得数据中的任务对象,然后加锁
synchronized(task.lock) {
if (task.state == TimerTask.CANCELLED) {
//如果任务已经被取消,则从队列中移除
queue.removeMin();
continue; // No action required, poll queue again
}
currentTime = System.currentTimeMillis();
executionTime = task.nextExecutionTime;
//判断该任务是否已经过时,如果没有过时,即可以触发
if (taskFired = (executionTime<=currentTime)) {
//判断是否要重复,如果不重复,从队列中删除,修改任务状态
if (task.period == 0) { // Non-repeating, remove
queue.removeMin();
task.state = TimerTask.EXECUTED;
} else { // Repeating task, reschedule
//重新计算下一次的执行时间,然后按执行时间排序
queue.rescheduleMin(
task.period<0 ? currentTime - task.period
: executionTime + task.period);
}
}
}
//如果任务还没有被触发,等待一定的时间,为了避免队列中只有一个任务由于出发时间不到出现死循环,因此进行等待,如果此时任务队列中加入了其他任务,就不会进行等待了
if (!taskFired) // Task hasn't yet fired; wait
queue.wait(executionTime - currentTime);
}
//如果任务已经触发了,则调用TimerTask的run方法,运行任务
if (taskFired) // Task fired; run it, holding no locks
task.run();
} catch(InterruptedException e) {
}
}
}
schedule
//delay 延迟时间 延迟任务
public void schedule(TimerTask task, long delay) {
if (delay < 0)
throw new IllegalArgumentException("Negative delay.");
sched(task, System.currentTimeMillis()+delay, 0);
}
//定时任务
public void schedule(TimerTask task, Date time) {
sched(task, time.getTime(), 0);
}
//延迟周期任务
public void schedule(TimerTask task, long delay, long period) {
if (delay < 0)
throw new IllegalArgumentException("Negative delay.");
if (period <= 0)
throw new IllegalArgumentException("Non-positive period.");
sched(task, System.currentTimeMillis()+delay, -period);
}
//定时周期任务
public void schedule(TimerTask task, Date firstTime, long period) {
if (period <= 0)
throw new IllegalArgumentException("Non-positive period.");
sched(task, firstTime.getTime(), -period);
}
//time:执行时间 period:间隔时间
private void sched(TimerTask task, long time, long period) {
if (time < 0)
throw new IllegalArgumentException("Illegal execution time.");
// Constrain value of period sufficiently to prevent numeric
// overflow while still being effectively infinitely large.
if (Math.abs(period) > (Long.MAX_VALUE >> 1))
period >>= 1;
synchronized(queue) {
if (!thread.newTasksMayBeScheduled)
throw new IllegalStateException("Timer already cancelled.");
synchronized(task.lock) {
if (task.state != TimerTask.VIRGIN)
throw new IllegalStateException(
"Task already scheduled or cancelled");
//设置任务的执行时间、间隔和状态
task.nextExecutionTime = time;
task.period = period;
task.state = TimerTask.SCHEDULED;
}
//将任务添加到队列中,并且根据任务的下一次执行时间进行排序
queue.add(task);
if (queue.getMin() == task)
//当该任务被加入到队列中,队列不为空,且它时执行时间最靠前的,TaskThread守护线程从queue.wait方法处被唤起
queue.notify();
}
}
优点与缺点
Timer 的优点在于简单易用,但由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务。
运行轨迹:
ScheduledThreadPoolExecutor
鉴于 Timer 的上述缺陷,Java 5 推出了基于线程池设计的 ScheduledThreadPoolExecutor。其设计思想是,每一个被调度的任务都会由线程池中一个线程去执行,因此如果是使用多线程去执行,则任务是并发执行的,相互之间不会受到干扰。需要注意的是,只有当任务的执行时间到来时,才会真正启动一个线程,其余时间都是在轮询任务的状态;如果是使用单线程池去执行,则任务还是串行执行的。
从上一章,可以看出底层和普通的线程池实现一致,只要是采用了具有延迟效果的队列DelayWorkQueue
DelayWorkQueue
DelayWorkQueue是基于阻塞队列DelayQueue实现的
static class DelayedWorkQueue extends AbstractQueue<Runnable>
implements BlockingQueue<Runnable> {
private static final int INITIAL_CAPACITY = 16;
private RunnableScheduledFuture<?>[] queue =
new RunnableScheduledFuture<?>[INITIAL_CAPACITY];
private final ReentrantLock lock = new ReentrantLock();
private int size = 0;
private final Condition available = lock.newCondition();
}
ScheduleExecutorService
上图是Java线程池的继承结构,ExecutorService中主要是线程池的通用方法的定义,ScheduleExecutorService中定义的是定时任务的4个方法定义。
schedule
public ScheduledFuture<?> schedule(Runnable command,
long delay,
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
//将任务包装为RunnableScheduledFuture
//triggerTIme用来计算触发时间
RunnableScheduledFuture<?> t = decorateTask(command,
new ScheduledFutureTask<Void>(command, null,
triggerTime(delay, unit)));
//推迟执行
delayedExecute(t);
return t;
}
private void delayedExecute(RunnableScheduledFuture<?> task) {
if (isShutdown())
reject(task);
else {
//将任务放到等待队列中
super.getQueue().add(task);
if (isShutdown() &&
!canRunInCurrentRunState(task.isPeriodic()) &&
remove(task))
task.cancel(false);
else
ensurePrestart();
}
}
void ensurePrestart() {
//判断线程池中正在执行的线程数,然后调用addWorker方法
int wc = workerCountOf(ctl.get());
if (wc < corePoolSize)
addWorker(null, true);
else if (wc == 0)
addWorker(null, false);
}
从上一章节的线程池中已经了解到addWorker(),这个方法会将任务封装为Worker对象,然后调用Worker对象的start()方法,进而执行任务的run()方法,这里执行的是ScheduledFutureTask对象的run方法
public void run() {
//该任务是否是间隔执行的
boolean periodic = isPeriodic();
if (!canRunInCurrentRunState(periodic))
cancel(false);
//如果任务不需要重复执行,调用父类的run方法
else if (!periodic)
ScheduledFutureTask.super.run();
//如果任务需要间隔执行,运行并将该任务重置状态,然后计算下一次的运行时间
else if (ScheduledFutureTask.super.runAndReset()) {
setNextRunTime();
reExecutePeriodic(outerTask);
}
}
scheduleAtFixedRate
每次执行时间为上一次任务开始起向后推一个时间间隔
scheduleWithFixedDelay
每次执行时间为上一次任务结束起向后推一个时间间隔
复杂任务调度
Timer 和 ScheduledThreadPoolExecutor 都仅能提供基于开始时间与重复间隔的任务调度,不能胜任更加复杂的调度需求。比如,设置每星期二的 16:38:10 执行任务。该功能使用 Timer 和 ScheduledExecutor 都不能直接实现,但我们可以借助 Calendar 间接实现该功能。
其核心在于根据当前时间推算出最近一个星期二 16:38:10 的绝对时间,然后计算与当前时间的时间差,作为调用 ScheduledExceutor 函数的参数。
通过Calendar取计算复杂任务调度的执行时间是很复杂的,这就需要一个更加完善的任务调度框架来解决这些复杂的调度问题,后面章节会介绍开源的框架Quartz。