Java内置定时器Timer
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的更多相关文章
- Java 性能分析工具 , 第 2 部分:Java 内置监控工具
引言 本文为 Java 性能分析工具系列文章第二篇,第一篇:操作系统工具.在本文中将介绍如何使用 Java 内置监控工具更加深入的了解 Java 应用程序和 JVM 本身.在 JDK 中有许多内置的工 ...
- 【子非鱼】冒泡排序过程呈现之java内置GUI表示
自己玩玩写写,排序的过程多么有趣,特别是把看着电脑吧一堆乱七八糟的数据排成有序组合的时候,看起来贼舒服,特别是强迫症患者.好了,话不多说上代码,也算是自己记录一下吧,没有什么技术含量但个人感觉比较有趣 ...
- 深入理解Java内置锁和显式锁
synchronized and Reentrantlock 多线程编程中,当代码需要同步时我们会用到锁.Java为我们提供了内置锁(synchronized)和显式锁(ReentrantLock)两 ...
- 使用Java内置的Http Server构建Web应用
一.概述 使用Java技术构建Web应用时, 我们通常离不开tomcat和jetty之类的servlet容器,这些Web服务器功能强大,性能强劲,深受欢迎,是运行大型Web应用的必备神器. 虽然Jav ...
- java内置线程池ThreadPoolExecutor源码学习记录
背景 公司业务性能优化,使用java自带的Executors.newFixedThreadPool()方法生成线程池.但是其内部定义的LinkedBlockingQueue容量是Integer.MAX ...
- Java内置包装类
Java内置包装类有Object.Integer.Float.Double.Number.Charcter.Boolean.Byte.System. Number,是抽象类,也是超类(父类).Numb ...
- java注解学习(1)注解的作用和三个常用java内置注解
今天,记录一下自己学习的关于注解方面的知识. Annotation是从JDK5.0开始引入的新技术 Annotation的作用: -不是程序本身,可以对程序做出解释(这一点和注释没什么区别) -可以被 ...
- 不使用java内置函数,将String字符串转换为int类型
package com.test; public class AtoiTest { public static void main(String[] args) throws Exception { ...
- 设计模式 - 观察者模式(Observer Pattern) Java内置 用法
观察者模式(Observer Pattern) Java内置 用法 本文地址: http://blog.csdn.net/caroline_wendy/article/details/26601659 ...
随机推荐
- K-means聚类分析
一.原理 先确定簇的个数,K 假设每个簇都有一个中心点 centroid 将每个样本点划分到距离它最近的中心点所属的簇中 选择K个点做为初始的中心点 while() { 将所有点分配个K个中心点形成K ...
- Rocket - diplomacy - ValName
https://mp.weixin.qq.com/s/so-2x5KLfYF0IMCCqNThwQ 简单调试ValName实现: 1. 使用 Desugar之后如下: ...
- Java实现 蓝桥杯 算法提高 摩尔斯电码
算法提高 9-3摩尔斯电码 时间限制:1.0s 内存限制:256.0MB 提交此题 问题描述 摩尔斯电码破译.类似于乔林教材第213页的例6.5,要求输入摩尔斯码,返回英文.请不要使用"zy ...
- Java实现 LeetCode 268 缺失数字
268. 缺失数字 给定一个包含 0, 1, 2, -, n 中 n 个数的序列,找出 0 - n 中没有出现在序列中的那个数. 示例 1: 输入: [3,0,1] 输出: 2 示例 2: 输入: [ ...
- Java实现固定长度得01子串
固定位数得01子串 Description 对于长度为n的一个01串,每一位都可能是0或1,一共有2 ^n 种可能.请按从小到大的顺序输出这2^n种01串. Input 包含多组数据,每组数据占一行, ...
- Java实现 蓝桥杯 历届试题 最大子阵
问题描述 给定一个n*m的矩阵A,求A中的一个非空子矩阵,使这个子矩阵中的元素和最大. 其中,A的子矩阵指在A中行和列均连续的一块. 输入格式 输入的第一行包含两个整数n, m,分别表示矩阵A的行数和 ...
- OO第一作业周期(前四周)总结
前言:回顾这三次的作业,在一次次的练习下渐渐理解了一些Java的一些基本知识和类与对象的含义与用法,也找到了很多自身的不足和问题,主要是反映类与类之间的关系没有理解到位,这次总结后又有了新的感悟和理解 ...
- 09_EM算法
今天是2020年3月5日星期四.预计开学时间不会早于四月初,真是好消息,可以有大把的时间整理知识点(实际上发文章的时间都6月6号了,希望9月份能开学啊,不耽误找工作~).每次导师找,整个人会变的特别烦 ...
- 第一次使用Genymotion遇到的问题:for an unknown reson,VirtualBox DHCP has not assigned an IP address to virtual
解决方案:http://www.aiuxian.com/article/p-554135.html
- 移动UI系列 - 简单地使用半衰期算法来预测手势的滑动方向与速度
前言 有一个问题, 给定一个物体的运动轨迹, 包含时间和坐标的数组, 如何使用这个数据来预测物体未来的运动走势?? 本文提供了一个很简单的方式去实现这个算法. 效果够用, 又简单, 有一定的准确程度. ...