Timer是Java内置的一个定时任务,类似于JavaScript里面的setTimeout()和setInterval()方法,可以延迟一定的时间执行任务,也可以按时间间隔重复执行任务。

Timer实际上就是启动了一个线程进行任务处理的,是异步的。

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask; public class Test {
public static int times = 1;
/**
* Timer基本用法
* */
public static void main(String[] args) {
Timer t = new Timer();
TimerTask task = new TimerTask() {
@Override
public void run() {
System.err.println(new Date() + " ==> " + Thread.currentThread().getName() + "第" + Test.times + "次执行");
Test.times++;
if(Test.times >= 5) {
System.err.println("定时器结束");
cancel();
}
}
};
t.schedule(task , 1000, 2000);
/**
* scheduleAtFixedRate和schedule 的区别
* schedule 是按任务真实执行时间来计算下次执行时间的。
* scheduleAtFixedRate 是按上个任务的计划执行时间来计算下次执行时间的。
* 举个例子:
* 任务的执行间隔是2s,首次执行延迟时长为1s,当前时间是15:00:00
* schedule:
* 第一次执行时间为15:00:01,下次执行时间为15:00:03
* 第二次执行时线程抢到CPU是在15:00:04,那第二次执行是在15:00:04,下次执行时间是(15:00:04 + 2s,也就是15:00:06)
* scheduleAtFixedRate:
* 第一次执行时间为15:00:01,下次执行时间为15:00:03
* 第二次执行时线程抢到CPU是在15:00:04,那第二次执行是在15:00:04,下次执行时间是(15:00:03 + 2s,也就是15:00:05)
* */
}
}

 Timer的相关方法

 先上点源码让你们看看,好有个大概印象

public class Timer {
/**
* 任务队列
*/
private final TaskQueue queue = new TaskQueue();
/**
* 定时器线程
*/
private final TimerThread thread = new TimerThread(queue);
/** 线程序号id */
private final static AtomicInteger nextSerialNumber = new AtomicInteger(0);
/**
* 获取下一个线程序号
* */
private static int serialNumber() {
return nextSerialNumber.getAndIncrement();
}
/**
* 构造函数,默认线程名是Timer-0
* */
public Timer() {
this("Timer-" + serialNumber());
}
/**
* 当我们一创建Timer对象,线程就已经启动了
* */
public Timer(String name) {
thread.setName(name);
thread.start();
}
/***
* 添加定时任务,以上一次执行任务的<真实时间>做为period的开始计算时间
* @param task 定时任务,实现了TimerTask的类的对象
* @param delay 延迟多少毫秒后再执行
* @param period 重复执行的时间间隔
* */
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.");
sched(task, System.currentTimeMillis()+delay, -period);
}
/***
* 添加定时任务,以上一次执行任务的<计划时间>做为period的开始计算时间
* @param task 定时任务,实现了TimerTask的类的对象
* @param delay 延迟多少毫秒后再执行
* @param 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.");
sched(task, System.currentTimeMillis()+delay, period);
}
/**
*
* */
private void sched(TimerTask task, long time, long period) {
if (time < 0)
throw new IllegalArgumentException("Illegal execution time."); // 如果时间间隔很大,将其除2,防止溢出,因为System.currentTimeMillis()也只是返回一个long型数值,你
// 一个period就已经足够大了,再加上当前系统毫秒数,就可能超多long的最大值了
if (Math.abs(period) > (Long.MAX_VALUE >> 1))
period >>= 1;
/**
* 这里是为了防止多线程下调用Timer的schedule方法。Timer里面的TimerThread线程也是会用到queue的,所以这里也是为了防止定时器线程和主线程冲突
* */
synchronized(queue) {
/** newTasksMayBeScheduled这个值是被写死为true的,只有任务结束,定时器完蛋的时候才会变成false */
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.wait()方法陷入阻塞的线程只有定时器的TimerThread的实例 */
queue.notify();
}
}
/**
* 取消定时器
* */
public void cancel() {
synchronized(queue) {
/** 这个状态前面已经说了很多遍了,这里要记住,因为后面任务循环会用到 */
thread.newTasksMayBeScheduled = false;
queue.clear();
queue.notify(); // In case queue was already empty.
}
}
} /***
* 用于Timer的定时器线程类
* */
class TimerThread extends Thread {
boolean newTasksMayBeScheduled = true;
/** 任务队列,记得Timer的构造函数么,Timer里面的TaskQueue和这个是同一个,指向同一个对象,这样子就是为了给线程传递一个共享变量 */
private TaskQueue queue;
/**
* 构造函数
* */
TimerThread(TaskQueue queue) {
this.queue = queue;
}
/**
* 这个不用多说你们也知道是啥吧
* */
public void run() {
try {
mainLoop();
} finally {
/**
* 这里必须要注意,Timer定时器一旦创建,只要没有发生异常,那他就是一直在不断循环等待任务执行的
* 不管你队列里面究竟还有没有任务。就算没有任务,那你能保证以后不会往这里面加任务么。
* <b>注意,如果我们想要关闭这个定时任务,那就显示调用Timer的cancel方法</b>
* */
synchronized(queue) {
newTasksMayBeScheduled = false;
queue.clear(); // Eliminate obsolete references
}
}
}
/**
* 主循环
* */
private void mainLoop() {
// 死循环
while (true) {
try {
TimerTask task;
boolean taskFired;
synchronized(queue) {
/**
* 如果队列为空,那么就判断newTasksMayBeScheduled是不是true,如果不是,那就用queue.wait()挂起线程
* 队列为空有3种情况:
* 1:刚创建,还没加任务,此时newTasksMayBeScheduled初始化为true
* 2:以前有任务,但是并不是重复任务,执行完就没了,此时newTasksMayBeScheduled不变,还是true
* 3:Timer的cancel()方法被调用了,清空了队列,此时newTasksMayBeScheduled被修改成false,前面有叫你们记住的
* */
while (queue.isEmpty() && newTasksMayBeScheduled)
queue.wait();
if (queue.isEmpty())
break; // Timer被取消了,就退出主循环。这是两种退出方法中的第一种,是正经的方法 // 从队列中找计划执行时间最早的任务
long currentTime, executionTime;
task = queue.getMin();
synchronized(task.lock) {
if (task.state == TimerTask.CANCELLED) {
queue.removeMin();
continue; // No action required, poll queue again
}
currentTime = System.currentTimeMillis();
executionTime = task.nextExecutionTime;
// 如果到了执行时间,那就取出这个任务
if (taskFired = (executionTime<=currentTime)) {
if (task.period == 0) { // Non-repeating, remove
queue.removeMin();
task.state = TimerTask.EXECUTED;
} else { // 如果这是一个重复任务,那就再更改一下他的计划执行时间,并且将把这个任务按执行时间先后,重新排下序
queue.rescheduleMin(
task.period<0 ? currentTime - task.period
: executionTime + task.period);
}
}
}
if (!taskFired) // 如果最小的任务还没到执行时间,那就先挂起,免得抢CPU
queue.wait(executionTime - currentTime);
}
if (taskFired) // 直接执行任务
task.run();
} catch(InterruptedException e) {
/**
* 结束定时器除了调用Timer的cancel()方法,还可以丢一个会报错的任务进来,不过不能保证什么时候执行,而且这样会让程序不健强,不推荐。
* 另外InterruptedException会被捕获
* */
}
}
}
}
/**
* 这个是Timer中用到的队列,看不看都无所谓
* */
class TaskQueue {
/** 任务数组,默认128个,但是这个任务数组会自动扩容,新容量 = 旧容量 * 2,另外要注意,整个数组所有元素都是按计划执行时间从小到大进行排序的,而且0下标是不放东西的 */
private TimerTask[] queue = new TimerTask[128];
/** 实际任务个数 */
private int size = 0;
/**
* 添加任务
* */
void add(TimerTask task) {
/** 如果容量不够塞下这个任务,就扩容 */
if (size + 1 == queue.length)
queue = Arrays.copyOf(queue, 2*queue.length); queue[++size] = task;
/** 这个 */
fixUp(size);
}
/**
* 因为本身数组就是有序的,所以这里计划执行时间最小的就是第一个元素
* */
TimerTask getMin() {
return queue[1];
}
/**
* 移除最小的一个
* */
void removeMin() {
queue[1] = queue[size];
queue[size--] = null; // Drop extra reference to prevent memory leak
fixDown(1);
}
/**
* 给最小的一个任务重新设置计划执行时间
* 因为重新设置时间后,他的计划时间可能大于后面的任务,所以会对这个任务进行降级处理
* */
void rescheduleMin(long newTime) {
queue[1].nextExecutionTime = newTime;
fixDown(1);
}
/**
* 对任务进行升级处理,也就是往队首移动。将这个元素的计划执行时间和前面的进行比较,将其放到一个合适的位置,保证队列数组始终顺序排列
* @param k 要调整位置的元素的下标
* */
private void fixUp(int k) {
while (k > 1) {
/** 还记得我们前面说过的,队列数组的0下标是废的么,这里就是原因。k >> 1,就是k / 2 ,我们总不能是0 / 2,所以就把0下标给废了 */
int j = k >> 1;
/** 这里使用的其实就是二分排序 */
if (queue[j].nextExecutionTime <= queue[k].nextExecutionTime)
break;
TimerTask tmp = queue[j]; queue[j] = queue[k]; queue[k] = tmp;
k = j;
}
}
/**
* 和fixUp相反,这个是降级处理,就是往队尾移动
* @param k 要调整位置的元素的下标
* */
private void fixDown(int k) {
int j;
while ((j = k << 1) <= size && j > 0) {
if (j < size &&
queue[j].nextExecutionTime > queue[j+1].nextExecutionTime)
j++; // j indexes smallest kid
if (queue[k].nextExecutionTime <= queue[j].nextExecutionTime)
break;
TimerTask tmp = queue[j]; queue[j] = queue[k]; queue[k] = tmp;
k = j;
}
}
}

看完源码,再看调用图,是不是感觉更了解了

Java内置定时器Timer的更多相关文章

  1. Java 性能分析工具 , 第 2 部分:Java 内置监控工具

    引言 本文为 Java 性能分析工具系列文章第二篇,第一篇:操作系统工具.在本文中将介绍如何使用 Java 内置监控工具更加深入的了解 Java 应用程序和 JVM 本身.在 JDK 中有许多内置的工 ...

  2. 【子非鱼】冒泡排序过程呈现之java内置GUI表示

    自己玩玩写写,排序的过程多么有趣,特别是把看着电脑吧一堆乱七八糟的数据排成有序组合的时候,看起来贼舒服,特别是强迫症患者.好了,话不多说上代码,也算是自己记录一下吧,没有什么技术含量但个人感觉比较有趣 ...

  3. 深入理解Java内置锁和显式锁

    synchronized and Reentrantlock 多线程编程中,当代码需要同步时我们会用到锁.Java为我们提供了内置锁(synchronized)和显式锁(ReentrantLock)两 ...

  4. 使用Java内置的Http Server构建Web应用

    一.概述 使用Java技术构建Web应用时, 我们通常离不开tomcat和jetty之类的servlet容器,这些Web服务器功能强大,性能强劲,深受欢迎,是运行大型Web应用的必备神器. 虽然Jav ...

  5. java内置线程池ThreadPoolExecutor源码学习记录

    背景 公司业务性能优化,使用java自带的Executors.newFixedThreadPool()方法生成线程池.但是其内部定义的LinkedBlockingQueue容量是Integer.MAX ...

  6. Java内置包装类

    Java内置包装类有Object.Integer.Float.Double.Number.Charcter.Boolean.Byte.System. Number,是抽象类,也是超类(父类).Numb ...

  7. java注解学习(1)注解的作用和三个常用java内置注解

    今天,记录一下自己学习的关于注解方面的知识. Annotation是从JDK5.0开始引入的新技术 Annotation的作用: -不是程序本身,可以对程序做出解释(这一点和注释没什么区别) -可以被 ...

  8. 不使用java内置函数,将String字符串转换为int类型

    package com.test; public class AtoiTest { public static void main(String[] args) throws Exception { ...

  9. 设计模式 - 观察者模式(Observer Pattern) Java内置 用法

    观察者模式(Observer Pattern) Java内置 用法 本文地址: http://blog.csdn.net/caroline_wendy/article/details/26601659 ...

随机推荐

  1. HTTP 冷知识 | HTTP 请求中,空格应该被编码为 %20 还是 + ?

    HTTP 请求中,空格应该被编码为什么?今天我们走进 RFC 文档和 W3C 文档,了解一下这个「史诗级」大坑. 1.%20 还是 + ? 开始讲解前先看个小测试,在浏览器里输入 blank test ...

  2. 啪啪,打脸了!领导说:try-catch必须放在循环体外!

    哈喽,亲爱的小伙伴们,技术学磊哥,进步没得说!欢迎来到新一期的性能解读系列,我是磊哥. 今天给大家带来的是关于 try-catch 应该放在循环体外,还是放在循环体内的文章,我们将从性能和业务场景分析 ...

  3. Rocket - debug - Example: Selecting Harts

    https://mp.weixin.qq.com/s/HjG5S9binyniG_amC3Dr5Q 介绍riscv-debug的使用实例:如何选择核心,执行Halt/Resume请求. 1. Sele ...

  4. jchdl - GSL实例 - Counter

    https://mp.weixin.qq.com/s/BjQtQE8DfaKP1XwcTiCwVg   ​​ 摘自康华光<电子技术基础 · 数字部分>(第五版)   参考链接 https: ...

  5. Take advantage of Checkra1n to Jailbreak iDevice for App analysis

    An unpatchable bootrom exploit called "checkm8" works on all iDevices up until the iPhone ...

  6. Java IO实现文件(及文件夹)的复制 原创代码【精】

    单个文件复制 FileInputStream input=new FileInputStream("C://360//fay.jpg"); FileOutputStream out ...

  7. Java实现 LeetCode 190 颠倒二进制位

    190. 颠倒二进制位 颠倒给定的 32 位无符号整数的二进制位. 示例 1: 输入: 00000010100101000001111010011100 输出: 0011100101111000001 ...

  8. Java实现 LeetCode 61 旋转链表

    61. 旋转链表 给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数. 示例 1: 输入: 1->2->3->4->5->NULL, k = ...

  9. Java实现 LeetCode 50 Pow(x,n)

    50. Pow(x, n) 实现 pow(x, n) ,即计算 x 的 n 次幂函数. 示例 1: 输入: 2.00000, 10 输出: 1024.00000 示例 2: 输入: 2.10000, ...

  10. XStrea学习手册

    ​​ 一.前言 1.XStream官网 http://x-stream.github.io 2.XStream是什么 XStream是一个简单的基于Java的类库,用来将Java对象序列化成XML(J ...