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. Linux 目录解析

      FHS:Filesystem Hierarchy Standard(文件系统目录标准)的缩写,多数Linux版本采用这种文件组织形式,类似于Windows操作系统中c盘的文件目录,FHS采用树形结 ...

  2. drf认证组件、权限组件、jwt认证、签发、jwt框架使用

    目录 一.注册接口 urls.py views.py serializers.py 二.登录接口 三.用户中心接口(权限校验) urls.py views.py serializers.py 四.图书 ...

  3. jsplumb 常用事件

    1. jsPlumb.getAllConnections() 获取所有连接线2. jsPlumb.deleteEveryConnection(); 清空所有连接线3. jsPlumb.deleteCo ...

  4. 基于 webGL 的元素周期表 3D 交互展示

    前言 之前在网上看到别人写的有关元素周期表的文章,深深的勾起了一波回忆,记忆里初中时期背的“氢氦锂铍硼,碳氮氧氟氖,钠镁铝硅磷,硫氯氩钾钙”.“养(氧)龟(硅)铝铁盖(钙),哪(钠)家(钾)没(镁)青 ...

  5. hadoop3自学入门笔记(3)-java 操作hdfs

    1.core-site.xml <configuration> <property> <name>fs.defaultFS</name> <val ...

  6. .net core 中api 模型验证

    AddControllers/AddMvc方法允许添加自定义ActionFilterAttribute进行过滤 文档中这么定义Filter: 可以创建自定义筛选器,用于处理横切关注点. 横切关注点的示 ...

  7. 5.Python安装依赖(包)模块方法介绍

    1.前提条件 1). 确保已经安装需要的Python版本 2). 确保已经将Python的目录加入到环境变量中 2. Python安装包的几种常用方式 1). pip安装方式(正常在线安装) 2). ...

  8. js删除对象数组

    若用remove删除某个对象数组,使用for循环遍历数组中的每个对象进行删除,则必须从数组的最后一个元素倒序删除,否则每次删除都只能删除数组的一半元素,因为把索引为0的子节点删除后那么很自然的原来索引 ...

  9. 拍摄UFP 单一职责原则

    3.1 新手机 3.2 拍摄 3.3 没用的东西 3.4 单一职责原则 就一个类而言,应该仅有一个引起它变化的原因, 3.5 方块游戏的设计 如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个 ...

  10. 硬盘500M,为什么没有500M。10M宽带,为什么网速没有10M?

    在天朝, 硬件厂商用1000代替1024, 通信公司,用 byte来代替bit. 比如 500G的硬盘,应该有 500 * 1024 *1024 *8 = 4.194304*10^9 位 但是按照厂商 ...