Java定时任务之Timer

Timer是Java中实现定时任务的方式之一,下面是一个简单的例子:

import java.util.Timer;
import java.util.TimerTask;

public class TimerStudy {

  public static void main(String[] argc) {
    Timer timer = new Timer();
    TimerTask timerTask = new TimerTask() {
      @Override public void run() {
        System.out.println("Hello, world!");
      }
    };
    timer.scheduleAtFixedRate(timerTask, 30_000, 30_000);
  }
}

从代码中可以看出,使用Timer实现定时任务,涉及到两个类:Timer和TimerTask,下面从源码来看看Timer和TimerTask的实现细节。

TimerTask源码如下:

public abstract class TimerTask implements Runnable {

    final Object lock = new Object();

    int state = VIRGIN;

    static final int VIRGIN = 0;

    static final int SCHEDULED   = 1;

    static final int EXECUTED    = 2;

    static final int CANCELLED   = 3;

    long nextExecutionTime;

    long period = 0;

    protected TimerTask() {
    }

    public abstract void run();

    public boolean cancel() {
        synchronized(lock) {
            boolean result = (state == SCHEDULED);
            state = CANCELLED;
            return result;
        }
    }

    public long scheduledExecutionTime() {
        synchronized(lock) {
            return (period < 0 ? nextExecutionTime + period
                               : nextExecutionTime - period);
        }
    }
}

从源码中可以看出,TimerTask实现了Runnable接口,因此定义TimerTask对象时,需要实现run方法来实现自己的任务。

TimerTask类有三个成员变量state, nextExecutionTime和period, 其中state表示定时任务的状态,取值范围如下:

  • VIRGIN(0): 表示定时任务还没有被调度执行,新建的定时任务默认为VIRGIN状态;
  • SCHEDULED(1): 表示定时任务已经被调度;
  • EXECUTED(2): 表示定时任务已经执行,或者正在执行,注意:只有非重复执行的定时任务才有此状态
  • CANCELLED(3): 表示定时任务已经被取消了,调用TimerTask.cancel()方法可以取消定时任务

nextExecutionTime表示定时任务下次执行的时间。

period表示定时任务两次执行的间隔,=0表示不需要重复执行,>0表示固定频率执行,<0表示固定延迟执行;固定频率和固定延迟的主要区别在于:固定频率下一次计划执行时间是按照上一次计划执行时间加上延迟计算的;而固定延迟是按照上次实际执行时间加上延迟来计算的。举个例子:假如定时任务A计划执行时间是01:00:00, 实际执行时间是01:02:00, 延迟是10分钟,那固定频率的下一次计划执行时间是01:10:00, 而固定延迟的下一次计划执行时间是01:12:00.

TimeTask主要的成员方法有cancel和scheduledExecutionTime, cancel方法用来取消定时任务,scheduledExecutionTime返回下一次计划执行时间。

scheduledExecution方法的代码如下:

public long scheduledExecutionTime() {
        synchronized(lock) {
            return (period < 0 ? nextExecutionTime + period
                               : nextExecutionTime - period);
        }
    }

该方法主要用来在run方法中调用,用来判断定时任务执行的是否及时;由于固定延迟的定时任务的执行时间可以往后偏移,因此固定延迟执行的定时任务调用次方法没有任何实际意义。

下面来看看Timer类的定义:

public class Timer {
    /**
     * 定时任务队列,通过Timer添加的TimerTask都由TaskQueue管理
     */
    private final TaskQueue queue = new TaskQueue();

    /**
     * 定时任务调度和执行线程
     */
    private final TimerThread thread = new TimerThread(queue);

    /**
     * 当TaskQueue中没有任务,且没有引用引用到Timer对象时,该对象可以使TimerThread优雅的退出
     */
    private final Object threadReaper = new Object() {
        protected void finalize() throws Throwable {
            synchronized(queue) {
                thread.newTasksMayBeScheduled = false;
                queue.notify(); // In case queue is empty.
            }
        }
    };

    /**
     * 用于生成TimerThread的名字
     */
    private final static AtomicInteger nextSerialNumber = new AtomicInteger(0);
    private static int serialNumber() {
        return nextSerialNumber.getAndIncrement();
    }

    public Timer();

    public Timer(boolean isDaemon);

    public Timer(String name);

    public Timer(String name, boolean isDaemon);

    public void schedule(TimerTask task, long delay);

    public void schedule(TimerTask task, Date time);

    public void schedule(TimerTask task, long delay, long period);

    public void schedule(TimerTask task, Date firstTime, long period);

    public void scheduleAtFixedRate(TimerTask task, long delay, long period);

    public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period);

    private void sched(TimerTask task, long time, long period);

    public void cancel();

     public int purge();
}

Timer类中主要的成员变量有两个:一个是TaskQueue类型的queue,用来存放定时任务TimerTask;另外一个是TimerThread类型的thread,用来进行定时任务的调度和执行。

Timer中添加定时任务的方法主要有两类,schedule和scheduleAtFixedRate, schedule方法添加的是固定延迟执行的定时任务;而scheduleAtFixedRate方法添加的是固定频率执行的定时任务,从下面的源码中可以看出主要区别:

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.");
        //注意:period传的是负值
        sched(task, System.currentTimeMillis()+delay, -period);
}
public void scheduleAtFixedRate(TimerTask task, long delay, long period) {
        if (delay < 0)
            throw new IllegalArgumentException("Negative delay.");
        if (period <= 0)
            throw new IllegalArgumentException("Non-positive period.");
        //注意:period传的是正值
        sched(task, System.currentTimeMillis()+delay, period);
}

schedule方法调用sched方法是,period传的负值,scheduleAtFixedRate方法调用sched方法时传的period是正值,正好对应前面所讲的固定速率和固定延迟的定时任务的区别。

sched方法只添加定时任务的主要逻辑所在,代码如下:

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)
            queue.notify();
    }
}

sched方法首先检查了TimerThread的newTasksMayBeScheduled,如果Timer已经被取消则判处异常。然后设置task的nextExecutionTime和period,并将task的状态修改为SCHEDULED. 然后将task添加到TaskQueue中,如果task是TaskQueue中第一个需要执行的任务,则调用queue.notify方法通知TaskThread,至于为什么需要调用notify方法,等学习TaskThread源码时再具体看看。

cancel方法用来取消所有的定时任务,源码如下:

public void cancel() {
    synchronized(queue) {
        thread.newTasksMayBeScheduled = false;
        queue.clear();
        queue.notify();  // In case queue was already empty.
    }
}

purge用来清理所有被取消的定时任务,源码如下:

public int purge() {
    int result = 0;

    synchronized(queue) {
     for (int i = queue.size(); i > 0; i--) {
         if (queue.get(i).state == TimerTask.CANCELLED) {
             queue.quickRemove(i);
             result++;
         }
     }

     if (result != 0)
         queue.heapify();
    }

    return result;
}

下面来看会下TaskQueue的源码:

class TaskQueue {

    private TimerTask[] queue = new TimerTask[128];

    private int size = 0;

    int size();

    void add(TimerTask task);

    TimerTask getMin();

    TimerTask get(int i);

    void removeMin();

    void quickRemove(int i);

    void rescheduleMin(long newTime);

    boolean isEmpty();

    void clear();

    private void fixUp(int k);

    private void fixDown(int k);

    void heapify();
}

TaskQueue用数组类存放TimerTask,并按照nextExecutionTime的大小构建最小堆,这样堆顶的就是执行时间最早的定时任务。

TimerThread的源码如下:

class TimerThread extends Thread {
    //设置为false,通知线程已经没有活着的引用执行Timer对象,使得TimerThread可以很优雅地退出
    boolean newTasksMayBeScheduled = true;

    //引用的是Timer中的queue对象
    private TaskQueue queue;

    TimerThread(TaskQueue queue);

    //调用mainLoop,实现定时任务调度和执行
    public void run();

    //具体的定时任务调度和执行逻辑
    private void mainLoop();
}

TimerThread继承了Thread类,主要的逻辑在mainLoop方法中,mainLoop方法源码如下:

private void mainLoop() {
    while (true) {
        try {
            TimerTask task;
            boolean taskFired;
            synchronized(queue) {
                // 如果任务队列为空,就等待,直到其它线程添加任务时,调用notify方法唤醒TimerThread
                while (queue.isEmpty() && newTasksMayBeScheduled)
                    queue.wait();
                if (queue.isEmpty())
                    break; // 此时队列为空,说明newTaskMayBeScheduled=false,已经不存在活着的引用执行Timer,所以TimerTask需要结束调度和执行定时任务

                // 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;  // 定时任务已经被取消,则删除定时任务,然后重新执行循环
                    }
                    currentTime = System.currentTimeMillis();
                    executionTime = task.nextExecutionTime;
                    if (taskFired = (executionTime<=currentTime)) {
                        if (task.period == 0) { // 非重复执行的定时任务,从队列中删除
                            queue.removeMin();
                            task.state = TimerTask.EXECUTED;
                        } else { // 需要重复执行的定时任务,计算下一次执行的时间,并重新放到队列中;从此处可以看出固定速率和固定延迟的定时任务的区别
                            queue.rescheduleMin(
                              task.period<0 ? currentTime   - task.period
                                            : executionTime + task.period);
                        }
                    }
                }
                if (!taskFired) // 如果最小堆顶的定时任务还未到执行时间,则调用wait超时,直到超时或者被其它线程调用notify方法唤醒
                    queue.wait(executionTime - currentTime);
            }
            if (taskFired)  // Task fired; run it, holding no locks
                task.run();
        } catch(InterruptedException e) {
        }
    }
}

mainLoop方法是一个while死循环,不断地从TaskQueue中获取定时任务来进行执行,如果位于最小堆顶的定时任务过了计划执行时间,就执行;否则就调用wait方法进行等待,直到超时,或者被其它线程调用notify方法唤醒。Timer可以通过设置TimerTask的newTaskMayBeScheduled为false来让TimerTask优雅的退出。另外mainLoop方法的源代码也可以看出,之所以需要在创建TimerTask对象时将TimerQueue对象传进来,是因为Timer和TimerTask之间通过TimerQueue进行同步;同时也可以看出固定速率和固定延迟执行的定时任务之间的区别。

从上面的源码学习中可以看出,Timer是使用了最小堆来进行定时任务调度,原理比较简单易懂。由于Timer使用单线程来执行定时任务,因此不适合有大量定时任务,执行时间比较久的需求,一旦某个定时任务执行时间太久,就存在影响其它定时任务执行的可能。

原文地址:https://www.cnblogs.com/superThanos/p/12206985.html

时间: 2024-10-14 04:02:28

Java定时任务之Timer的相关文章

java定时任务类Timer和TimerTask用法详解

原文:java定时任务类Timer和TimerTask用法详解 代码下载地址:http://www.zuidaima.com/share/1550463277550592.htm package com.zuidaima.util; import java.io.IOException; import java.util.Timer; import test.MyTask; /* * 本类给出了使用Timer和TimerTaske的主要方法,其中包括定制任务,添加任务 * 退出任务,退出定时器.

java定时任务之 Timer

这几天一直在搞一个预警监控后台程序,其他都搞定了,最纠结的就是那个每天固定时间执行的问题,纠结的原因主要是对Timer的方法没有搞清楚,timer.schedule(TimerTask task,long l1,long l2);可以很明显的知道在l1时间段以后执行task,然后每l2时间执行一次, 可是timer.schedule(TimerTask task,Date l1,long l2);刚开始没有搞太清楚,总是以为是l1的date控制的每天执行一次,忽略了l2的存在,总是在date上边

详解java定时任务---Timer篇

一.简介      在java的jdk中提供了Timer.TimerTask两个类来做定时任务. Timer是一种定时器工具,用来在一个后台线程计划执行指定任务,而TimerTask一个抽象类,它的子类代表一个可以被Timer计划的任务.--------------------- Timer类      在工具类Timer中,提供了四个构造方法,每个构造方法都启动了计时器线程,同时Timer类可以保证多个线程可以共享单个Timer对象而无需进行外部同步,所以Timer类是线程安全的.但是由于每一

具体解释java定时任务

在我们编程过程中假设须要运行一些简单的定时任务,无须做复杂的控制.我们能够考虑使用JDK中的Timer定时任务来实现. 以下LZ就其原理.实例以及Timer缺陷三个方面来解析java Timer定时器. 一.简单介绍 在java中一个完整定时任务须要由Timer.TimerTask两个类来配合完毕. API中是这样定义他们的.Timer:一种工具.线程用其安排以后在后台线程中运行的任务.可安排任务运行一次,或者定期反复运行.由TimerTask:Timer 安排为一次运行或反复运行的任务.我们能

atititt.java定时任务框架选型Spring Quartz 注解总结

atititt.java定时任务框架选型Spring Quartz 总结 1. .Spring Quartz  (ati recomm) 1 2. Spring Quartz具体配置 2 2.1. 增加context,task命名空间xml: 2 2.2. 增加xsi:schemaLocation valide 2 2.3. 我们的task任务扫描注解in spr.xml 2 2.4. 设置运行方法 3 2.5. 设置输出日志 3 3. 运行测试sprX走ok兰. 4 4. Quartz Sch

Java 定时任务

在我们编程过程中如果需要执行一些简单的定时任务,无须做复杂的控制,我们可以考虑使用JDK中的Timer定时任务来实现.下面LZ就其原理.实例以及Timer缺陷三个方面来解析JavaTimer定时器. 一.简介 在Java中一个完整定时任务需要由Timer.TimerTask两个类来配合完成. API中是这样定义他们的,Timer:一种工具,线程用其安排以后在后台线程中执行的任务.可安排任务执行一次,或者定期重复执行.由TimerTask:Timer 安排为一次执行或重复执行的任务.我们可以这样理

Java定时任务器

java定时任务,每天定时执行任务.以下是这个例子的全部代码. public class TimerManager { //时间间隔 private static final long PERIOD_DAY = 24 * 60 * 60 * 1000; public TimerManager() { Calendar calendar = Calendar.getInstance(); /*** 定制每日2:00执行方法 ***/ calendar.set(Calendar.HOUR_OF_DA

Java定时任务示例

package com.my.timer; import java.util.Date; import java.util.TimerTask; public class myTask extends TimerTask { /** * 定时任务调用的方法,即处理业务的方法 */ @Override public void run() { System.out.println("定时任务执行了:" + new Date().toLocaleString()); } } package

java定时任务,每天定时执行任务

java定时任务,每天定时执行任务. public class TimerManager { //时间间隔 private static final long PERIOD_DAY = 24 * 60 * 60 * 1000; public TimerManager() { Calendar calendar = Calendar.getInstance(); /*** 定制每日2:00执行方法 ***/ calendar.set(Calendar.HOUR_OF_DAY, 2); calend