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. Rocket - tilelink - Bundles

    https://mp.weixin.qq.com/s/jrqBg2AIpQogBrpwNXjmwg   简单介绍Bundles文件中对TileLink规范(1.7.1)的定义. 参考链接:https: ...

  2. meta个人学习纪录

    < meta > 元素 元素可提供相关页面的元信息(meta-information),比如针对搜索引擎和更新频度的描述和关键词. 标签位于文档的头部,不包含任何内容. 标签的属性定义了与 ...

  3. 【Flume】安装与测试

    1.下载安装包http://archive.apache.org/dist/flume/ 2.解压命令tar -zxvf 压缩包 -C 路径 3.配置环境变量 export FLUME_HOME=/o ...

  4. Pycharm激活码测试有效,测试时间:2020-1-14可用

    812LFWMRSH-eyJsaWNlbnNlSWQiOiI4MTJMRldNUlNIIiwibGljZW5zZWVOYW1lIjoi5q2j54mIIOaOiOadgyIsImFzc2lnbmVlT ...

  5. Java实现 蓝桥杯VIP 算法训练 递归求二进制表示位数

    问题描述 给定一个十进制整数,返回其对应的二进制数的位数.例如,输入十进制数9,其对应的二进制数是1001,因此位数是4. 样例输入 一个满足题目要求的输入范例. 9 样例输出 与上面的样例输入对应的 ...

  6. javascript内置函数提供的显式绑定

    内置函数提供的显式绑定 最近在开发中遇到使用arr.map(module.fun) 这样的写法时(在一个模块调用了另外一个模块的方法), 造成了函数中this丢失的问题, 显示为undefined, ...

  7. Java培训Day02——制作疫情地图(一)

    一.前言 此次培训,是为期三天的网上培训.最终的目的是制作出疫情地图.首先我们来看看主要的讲课内容大纲. Day1 |-Java语法学习(个人感觉讲得还可以,主要围绕本次培训作出的讲解,没有像网上的基 ...

  8. Flask 的请求与响应

    flask的请求与响应 from flask import Flask,request,make_response,render_template,redirect app = Flask(__nam ...

  9. QToolTip 设置提示信息

    import sys from PyQt5.QtWidgets import (QWidget, QToolTip, QPushButton, QApplication) from PyQt5.QtG ...

  10. Spring系列.依赖注入配置

    依赖注入的配置 Spring的依赖注入分为基于构造函数的依赖注入和基于setter方法的依赖注入. 基于构造函数的依赖注入 <!-- 通过构造器参数索引方式依赖注入 --> <bea ...