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 ...
随机推荐
- 容器技术之Dockerfile (一)
在前边的随笔中我们聊到了docker的基本命令,镜像,网络,存储卷以及基于现有容器制做docker镜像,相关随笔可参考https://www.cnblogs.com/qiuhom-1874/categ ...
- Rocket - tilelink - SRAM
https://mp.weixin.qq.com/s/-z9n6SHyAiK2OE7mOSvC2Q 简单介绍SRAM的实现. 1. 基本介绍 实现一个支持读写的静态存储器.存取的 ...
- 跨域解决方案 - 跨域资源共享cors
目录 1. cors 介绍 2. 原理 3. cors 解决跨域 4. 自定义HTTP 头部字段解决跨域 5. 代码演示 5. 参考链接 1. cors 介绍 cors 说的是一个机制,其实相当于一个 ...
- C# Winform 学习(五)
目标 1.MDI应用程序 2.图片框控件 3.图片集控件 4.定时器控件 一.MDI应用程序 1.理解: 单文档界面:SDI(word) 多文档界面:MDI(excel) 2.特点: 1)每个MDI程 ...
- Java实现蓝桥杯七对数字
今有7对数字:两个1,两个2,两个3,-两个7,把它们排成一行. 要求,两个1间有1个其它数字,两个2间有2个其它数字,以此类推,两个7之间有7个其它数字.如下就是一个符合要求的排列: 1712642 ...
- Java实现填写乘法算式
观察下面的算式: * * × * * = * * * 它表示:两个两位数字相乘,结果是3位数.其中的星号(*)代表任意的数字,可以相同,也可以不同,只要不是在首位的就可以是0.当然,满足这个要求的算式 ...
- js数据劫持 Object.defineProperty() 作用
原生js Object.defineProperty() 作用 假设我们有一个obj对象,我们要给他设置一个name属性会这么做 Object.defineProperty()也可以设置对象属性 这个 ...
- python3 后台维护软件
后台维护软件 一.思路: 登录: 1.用户登录(编写GUI用户登录界面) 使用模块:tkinter,pymssql 验证逻辑: 1.获取文本框输入数据. 2.进行空值值判断 if ...else... ...
- Python内置Turtle绘图库方法简介+多案例
urtle库是Python语言中一个很流行的绘制图像的函数库,想象一个小乌龟,在一个横轴为x.纵轴为y的坐标系原点,(0,0)位置开始,它根据一组函数指令的控制,在这个平面坐标系中移动,从而在它爬行的 ...
- 宇宙第一IDE是谁?
更多精彩文章,尽在码农翻身 微服务把我坑了 如何降低程序员的工资? 程序员,你得选准跑路的时间! 两年,我学会了所有的编程语言! 一直CRUD,一直996,我烦透了,我要转型 字节码万岁! 上帝托梦给 ...