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使用单线程来执行定时任务,因此不适合有大量定时任务,执行时间比较久的需求,一旦某个定时任务执行时间太久,就存在影响其它定时任务执行的可能。

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

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

    一.简介      在java的jdk中提供了Timer.TimerTask两个类来做定时任务. Timer是一种定时器工具,用来在一个后台线程计划执行指定任务,而TimerTask一个抽象类,它的子 ...

  2. java定时任务Timer与ScheduledExecutorService<转>

    在我们编程过程中如果需要执行一些简单的定时任务,无须做复杂的控制,我们可以考虑使用JDK中的Timer定时任务来实现.下面LZ就其原理.实例以及Timer缺陷三个方面来解析java Timer定时器. ...

  3. 详解java定时任务

    在我们编程过程中如果需要执行一些简单的定时任务,无须做复杂的控制,我们可以考虑使用JDK中的Timer定时任务来实现.下面LZ就其原理.实例以及Timer缺陷三个方面来解析JavaTimer定时器. ...

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

    atititt.java定时任务框架选型Spring Quartz 总结 1. .Spring Quartz  (ati recomm) 1 2. Spring Quartz具体配置 2 2.1. 增 ...

  5. java定时任务

    java定时任务实现方法: public class TimingTask { private static int count = 0; private static SpiderService s ...

  6. 具体解释java定时任务

    在我们编程过程中假设须要运行一些简单的定时任务,无须做复杂的控制.我们能够考虑使用JDK中的Timer定时任务来实现. 以下LZ就其原理.实例以及Timer缺陷三个方面来解析java Timer定时器 ...

  7. Java定时任务器

    java定时任务,每天定时执行任务.以下是这个例子的全部代码. public class TimerManager { //时间间隔 private static final long PERIOD_ ...

  8. 设置定时任务(Timer类的介绍)

    设置定时任务(Timer类的介绍) 在我们的很多项目中,我们都须要用到定时任务,因此想借此博文来对定时任务进行一个介绍. 设置定时任务过程例如以下: 先new一个Timer对象 Timer timer ...

  9. 【定时任务】Timer

    Java原生api Timer类就可以实现简单的定时任务.下面将简单介绍一下Timer. 一.使用 Timer 实现定时任务 具体代码如下. 可以看到我们主要是分三步进行的 1.new Timer() ...

随机推荐

  1. OSPF理论

    OSPF简介 OSPF(Open Shortest Path First 开放式最短路径优先)协议是IETF为IP网络开发的IGP路由选择协议.它是一种典型的链路状态(link-state)路由协议. ...

  2. $.getJSON获取json数据失败

    首先简单介绍下 $.ajax  $.get  $.post  $.getJSON 的区别和用法 $.ajax中有一个type属性,专门用来指定是get请求还是post请求的分别对应的就是$.get和$ ...

  3. SAP 事务代码 ME31K 报错 - 不含来自带工厂分配的供应商的物料的采购没有被定义 -

    SAP 事务代码 ME31K 报错 - 不含来自带工厂分配的供应商的物料的采购没有被定义 - 近日收到业务团队的报错说,试图创建合同,报错如下: 不含来自带工厂分配的供应商的物料的采购没有被定义 这个 ...

  4. 剑指offer-字符的所有组合,复制复杂链表,二叉树中和为某一值的路径

    字符的所有组合 描述: 输入一个字符串,求这个字符串中的字符的所有组合.如:"abc",组合为"a" "b" c" "a ...

  5. 基于SSM开发在线家教预约系统源码

    开发环境: Windows操作系统开发工具:Eclipse+Jdk+Tomcat8+mysql数据库 注意:次项目运行Tomcat8服务器里面 次项目比较大,需要自行研究 运行效果图 源码及原文链接: ...

  6. scanf 与fgets

    scanf: 1.以输入字符串也可以输入数字 . 2.遇到空格就停止.3.会有segmentation fault. fgets: 1.只能输入字符串.2.回车才会停止.3.不会有segmenntat ...

  7. API网关服务:Spring Cloud Zuul

    最近在学习Spring Cloud的知识,现将API网关服务:Spring Cloud Zuul 的相关知识笔记整理如下.[采用 oneNote格式排版]

  8. NIO学习笔记,从Linux IO演化模型到Netty—— Netty零拷贝

    Netty的中零拷贝与上述零拷贝是不一样的,它并不是系统层面上的零拷贝,只是相对于ByteBuf而言的,更多的是偏向于数据操作优化这样的概念. Netty中的零拷贝: 1.CompositeByteB ...

  9. 刷题79. Word Search

    一.题目说明 题目79. Word Search,给定一个由字符组成的矩阵,从矩阵中查找一个字符串是否存在.可以连续横.纵找.不能重复使用,难度是Medium. 二.我的解答 惭愧,我写了很久总是有问 ...

  10. exp导出含有双引号的表

    由于ORACLE默认的表名都是不区分大小写,在创建表时,在数据字典中存储的表名为大写.在有些情况下,如果创建的表在表名上加上双引号("),则创建的表其表名在数据字典中不作转换.比如 SQL& ...