JDK源码阅读(十一):java定时任务,Timer和ScheduleThreadPoolExecutor

本文深入探讨JDK中的定时任务实现,包括Timer和ScheduledThreadPoolExecutor。Timer简单易用,但任务串行执行,而ScheduledThreadPoolExecutor基于线程池,任务并发执行。文中还分析了两者的核心设计、优缺点以及schedule系列方法的工作原理,并讨论了如何通过Calendar实现复杂任务调度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


任务调度是指基于给定时间点,给定时间间隔或者给定执行次数自动执行任务。

四种任务调度的 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();
    }
  }
}

运行结果:

1555488776844

设计核心

使用 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 的优点在于简单易用,但由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务。

运行轨迹:

img

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个方法定义。

ScheduleExecutorService的通用方法

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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值