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使用单线程来执行定时任务,因此不适合有大量定时任务,执行时间比较久的需求,一旦某个定时任务执行时间太久,就存在影响其它定时任务执行的可能。
Java定时任务之Timer的更多相关文章
- 详解java定时任务---Timer篇
一.简介 在java的jdk中提供了Timer.TimerTask两个类来做定时任务. Timer是一种定时器工具,用来在一个后台线程计划执行指定任务,而TimerTask一个抽象类,它的子 ...
- java定时任务Timer与ScheduledExecutorService<转>
在我们编程过程中如果需要执行一些简单的定时任务,无须做复杂的控制,我们可以考虑使用JDK中的Timer定时任务来实现.下面LZ就其原理.实例以及Timer缺陷三个方面来解析java Timer定时器. ...
- 详解java定时任务
在我们编程过程中如果需要执行一些简单的定时任务,无须做复杂的控制,我们可以考虑使用JDK中的Timer定时任务来实现.下面LZ就其原理.实例以及Timer缺陷三个方面来解析JavaTimer定时器. ...
- atititt.java定时任务框架选型Spring Quartz 注解总结
atititt.java定时任务框架选型Spring Quartz 总结 1. .Spring Quartz (ati recomm) 1 2. Spring Quartz具体配置 2 2.1. 增 ...
- java定时任务
java定时任务实现方法: public class TimingTask { private static int count = 0; private static SpiderService s ...
- 具体解释java定时任务
在我们编程过程中假设须要运行一些简单的定时任务,无须做复杂的控制.我们能够考虑使用JDK中的Timer定时任务来实现. 以下LZ就其原理.实例以及Timer缺陷三个方面来解析java Timer定时器 ...
- Java定时任务器
java定时任务,每天定时执行任务.以下是这个例子的全部代码. public class TimerManager { //时间间隔 private static final long PERIOD_ ...
- 设置定时任务(Timer类的介绍)
设置定时任务(Timer类的介绍) 在我们的很多项目中,我们都须要用到定时任务,因此想借此博文来对定时任务进行一个介绍. 设置定时任务过程例如以下: 先new一个Timer对象 Timer timer ...
- 【定时任务】Timer
Java原生api Timer类就可以实现简单的定时任务.下面将简单介绍一下Timer. 一.使用 Timer 实现定时任务 具体代码如下. 可以看到我们主要是分三步进行的 1.new Timer() ...
随机推荐
- IntelliJ 更改项目使用的 JDK 版本
在当前使用的 IntelliJ 中的 JDK 版本为 1.8,如何修改 IntelliJ 使用的 JDK 版本为 1.11 呢? 你可以在 IntelliJ 中进行修改. 选择 File 后,然后选择 ...
- The finally block does not always execute in try finally
A finally block does not always xecute. The code in the try block could go into an infinite loop, th ...
- Hadoop之HDFS扩容方法
HDFS就是用来存取数据的,那么当数据太多的时候存不下,我们必需扩充硬盘容量,或者换个更大的硬盘. 由于它是分布式文件系统,有两种扩充HDFS集群容量的方法:横向扩容和纵向扩容 横向扩容 横向扩容就是 ...
- include 和require 区别
include和require的区别 1.include() 包含文件 2.include_once(filename)如果已经包含,则不再执行include_once 3.requirerequi ...
- The Ether 靶场
0x01 首先对靶场进行端口扫描 发现只开启了80端口和22端口 0x02 目录扫描 访问了几个目录并没有什么发现 0x03 访问主页几个网站链接 发现了一个疑似文件包含的漏洞 0x04 抓包进行分析 ...
- 基于tensorflow2.0和cifar100的VGG13网络训练
VGG是2014年ILSVRC图像分类竞赛的第二名,相比当年的冠军GoogleNet在可扩展性方面更胜一筹,此外,它也是从图像中提取特征的CNN首选算法,VGG的各种网络模型结构如下: 今天代码的原型 ...
- linux cpp (接口与实现的分离)
以下是 .h 文件,是接口. 以下是函数的实现 以下是主函数 首先是以上两个文件编译,不用编译头文件 g++ -c gradeBook.cpp g++ -c gradeBook.main.cpp 之后 ...
- 【学习笔记】B站-2019-NLP(自然语言处理)之 BERT 课程 -- 相关课程笔记
BERT 课程笔记 1. 传统方案遇到的问题 BERT的核心在于Transformer,Transformer就类似seq2seq网络输入输出之间的网络结构. 传统的RNN网络:最大的问题,因为不能并 ...
- nodejs编程优化
如何编写优化的 JavaScript 对象属性的顺序:始终以相同的顺序实例化对象属性,以便共享的隐藏类和随后优化的代码可以共享之. 动态属性:在实例化之后向对象添加属性将强制执行隐藏的类更改,并降 ...
- jQuery---jquery.color.js和jquery.lazyload.js的使用
jquery.color.js的使用 了解即可 <!--1. 引入jquery的js文件--> <script src="jquery-1.12.4.js"> ...