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. [Python]获取字典所有值

    方法一:Key Value 直接获取 databases = {1: 'Student', 2: 'School'} for k,v in databases.items(): print(k,v) ...

  2. 使用requests、BeautifulSoup、线程池爬取艺龙酒店信息并保存到Excel中

    import requests import time, random, csv from fake_useragent import UserAgent from bs4 import Beauti ...

  3. C#的静态工厂方法与构造函数对比

    最近,在与同事进行协同编程时,我们开始讨论在C#中初始化新对象的最佳方法.我一直是使用构造函数实现,尽管他倾向于静态工程方法.这引起了关于每种类型的利弊的大量来来回回的讨论. 为了说明我所说的内容,这 ...

  4. MD5加密常用js库:crypto-js

    学习链接:https://github.com/brix/crypto-js

  5. linux--解决celery消息中间件带来的一系列问题

    启动celery定时任务 1.celery -A OpsManage beat -l info -S django 2.celery -A OpsManage worker -l info 此时消息中 ...

  6. 优秀 .NET 开源项目集锦

    Github 地址: https://github.com/jasonhua95/awesome-dotnet-core awesome-dotnet-core .NET Core框架.库和软件的中文 ...

  7. 如何实现广告响应式滚动 OwlCarousel2

    githu    https://github.com/OwlCarousel2/OwlCarousel2 OwlCarousel2 官方网址    http://owlcarousel2.githu ...

  8. SSH-Secure-Shell 3.2.9 build283版本,创建直接打开文件传输的快捷方式的方法

    2019-12-31 16:21:23 版本信息: 在安装目录下新建快捷方式 目标填写:"D:\SSH-Secure-Shell\SshClient.exe" /f 图标选择,系统 ...

  9. 如何知道一个路由器的 BSSID ?

    使用 Mac 连接上这个路由器,然后使用 option 按 wifi 按钮,可以在详情页里找到. 有些路由中继的设置需要使用 BSSID ,比如 pandorabox openwrt

  10. minio gateway 代理s3 存储

    以前有写过使用minio gataway 代理nas 的,实际上还可以代理s3 ,hdfs....,以下是一个gatway 带来nas 以及s3 集成的模式 环境准备 docker-compose文件 ...