java--Timer 定时器
Posted 高高for 循环
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java--Timer 定时器相关的知识,希望对你有一定的参考价值。
Timer 定时器
为什么要使用定时器呢?
- 比如说一个web应用,如果这个应用规模很大,那它的日志数据是不是很多。如果一直存下来服务器的存储量怕是不行吧,需要隔一段时间删除,那么就需要一个线程每隔一段时间去删除日志数据。
概念:Java java.util.Timer
Java java.util.Timer是一种定时器工具,用来在一个后台线程计划执行指定任务。它可以计划执行一个任务一次或反复多次.
概念:Java TimerTask
1.TimerTask一个抽象类,它的子类代表一个可以被Timer计划的任务。具体的任务在TimerTask中run接口中实现。通过Timer中的schedule方法启动定时任务。
Timer timer = new Timer();
timer.schedule(new TimerTask() {
public void run() {
System.out.println("11232");
}
}, 200000 , 1000);
Timer 类讲解
Timer.java中含有3个类:Timer、TimerThread、TaskQueue。
TaskQueue
- TaskQueue中存放一些列将要执行的TimerTask,以数组的形式存放,下标约小(注:下标为0不处理,即使用的最小下标为1),则表明优先级越高。
TimerThread
- TimerThread继承Thread类,会一直从TaskQueue中获取下标为1的TimerTask进行执行。并根据该TimerTask是否需要重复执行来决定是否放回到TaskQueue中。
Timer
- Timer用于配置用户期望的任务执行时间、执行次数、执行内容。它内部会配置,调度TimerThread、TaskQueue。
Timer 构造方法:
- Timer timer = new Timer(); //其中会调用this(“Timer-” + serialNumber());, 即它以Timer+序列号为该定时器的名字
- Timer timer = new Timer(String name); //以name作为该定时器的名字
- Timer timer = new Timer(boolean isDeamon); //是否将此定时器作为守护线程执行
- Timer timer = new Timer(name, isDeamon); //定时器名字, 是否为守护线程
创建一个 Timer 对象就是新启动了一个线程,但是这个新启动的线程,并不是守护线程,它一直在后台运行.
运行定时器
- 启动一个定时器实质是启动一个线程
- 所有的task都是TimerTask的子类
- 所有time都是Date类型的日期
- 所有delay和period都是long类型的延迟时间, 单位为毫秒
API:
schedule
-
timer.schedule(task, time);
在time时间执行task任务1次 -
timer.schedule(task, delay);
在延迟delay毫秒后执行task任务1次 -
timer.schedule(task, firstTime, period);
在firsttime时间执行task1次,之后定期period毫秒时间执行task, 时间如果为过去时间,不会执行过去没有执行的任务, 但是会马上执行 -
timer.schedule(task, delay, period);
在延迟delay后执行task1次,之后定期period毫秒时间执行task, 时间如果为过去时间, 不会执行过去没有执行的任务, 但是会马上执行
scheduleAtFixedRate
- timer.scheduleAtFixedRate(task, firstTime, period);
在firstTime时间执行task一次, 以后每隔period毫秒执行1次, 时间如果为过去时间, 会执行过去没有执行的任务, 但是会马上执行 - timer.scheduleAtFixedRate(task, delay, period);
在delay毫秒后执行task一次, 以后每隔period毫秒执行1次, 时间如果为过去时间, 会执行过去没有执行的任务, 但是会马上执行
启动任务schedule 与 scheduleAtFixedRate的区别
- 方法schedule 和方法 scheduleAtFixedRate 在使用上基本没什么差别,就是 scheduleAtFixedRate 具有追赶执行性,
- 什么意思呢?就是如果任务 在周期性运行过程中被打断了,scheduleAtFixedRate会尝试把之前落下的任务补上运行。而schedule就不管了,接着运行接下来的任务就行了.
1、在指定日期运行定时器任务,只运行一次
如果date日期在今天之前,则启动定时器后,立即运行一次定时任务run方法
如果date日期在今天之后,则启动定时器后,会在指定的将来日期运行一次任务run方法
public static void main(String[] args) throws ParseException {
String sdate = "2018-02-14";
SimpleDateFormat sf = new SimpleDateFormat("yy-MM-dd");
Date date = sf.parse(sdate);
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("系统正在运行……");
}
}, date); //在指定的日期运行一次定时任务
/*如果date日期在今天之前,则启动定时器后,立即运行一次定时任务run方法*/
/*如果date日期在今天之后,则启动定时器后,会在指定的将来日期运行一次任务run方法*/
}
2、在距当前时刻的一段时间后运行定时器任务,只运行一次
指定启动定时器5s之后运行定时器任务run方法,并且只运行一次
public static void main(String[] args) throws ParseException {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("系统正在运行……");
}
}, 5000); //指定启动定时器5s之后运行定时器任务run方法,并且只运行一次
}
3、在指定的时间后,每隔指定的时间,重复运行定时器任务
如果指定的date时间是当天或者今天之前,启动定时器后会立即每隔2s运行一次定时器任务
如果指定的date时间是未来的某天,启动定时器后会在未来的那天开始,每隔2s执行一次定时器任务
public static void main(String[] args) throws ParseException {
String sdate = "2018-02-10";
SimpleDateFormat sf = new SimpleDateFormat("yy-MM-dd");
Date date = sf.parse(sdate);
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("系统正在运行……");
}
}, date, 2000);
/*如果指定的date时间是当天或者今天之前,启动定时器后会立即每隔2s运行一次定时器任务*/
/*如果指定的date时间是未来的某天,启动定时器后会在未来的那天开始,每隔2s执行一次定时器任务*/
}
4、在距当前时刻的一段指定距离后,每隔指定时间运行一次定时器任务
当启动定时器后,5s之后开始每隔2s执行一次定时器任务
public static void main(String[] args) throws ParseException {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("系统正在运行……");
}
}, 5000, 2000);
/*当启动定时器后,5s之后开始每隔2s执行一次定时器任务*/
}
停止定时器
停止定时器实质是终止Timer的线程。默认情况下,创建的Timer线程会一直执行,如果要停止的话主要有以下四种方法终止Timer线程:
- 调用Timer的cancel方法;
- 把Timer线程设置成Daemon守护线程,当所有的用户线程结束后,那么守护线程也会被终止;
- 当所有的任务执行结束后,删除对应Timer对象的引用,线程也会被终止;
- 调用System.exit方法终止程序
举例用cancel方法终止Timer线程
public static void main(String[] args) throws InterruptedException {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("系统正在运行……");
// timer.cancel(); //可以在任何时刻调用cancel方法终止timer线程
}
}, 5000, 2000);
/*如果主线程不休眠一段时间,就执行了cancel方法,那么定时器还没来得及执行就会被关闭*/
Thread.sleep(6000);
timer.cancel();
}
举例让timer线程成为一个daemon守护线程
- 可以在创建timer时使用new Timer(true)达到这个目地,这样当程序只有daemon线程的时候,它就会自动终止运行。
public static void main(String[] args) throws ParseException {
Timer timer = new Timer(true);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("系统正在运行……");
}
}, 5000, 2000);
/*当启动定时器后,5s之后开始每隔2s执行一次定时器任务*/
}
一些注意的问题
- 每一个Timer仅对应唯一一个线程。
- Timer不保证任务执行的十分精确。
- Timer类的线程安全的。
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) {
//判断Timer是否已经取消
if (!thread.newTasksMayBeScheduled)
throw new IllegalStateException("Timer already cancelled.");
synchronized(task.lock) {
//TimerTask.VIRGIN标记任务没有被调度
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)//将TimerTask放到队列中,并进行队列排序
queue.notify();//如果队列里恰好下标为1的任务为当前的task,则直接唤醒
//注意最小的值是从下标为1的获取,queue[0]其实没用到
}
}
- 数据已经放到queue中了,那么看下是什么时候执行的。在之前Timer的构造函数这块,有一句是:thread.start();说明TimerThread在Timer初始化之后就一直启用着,那看下它的处理。
TimerThread的run() 方法:
public void run() {
try {
mainLoop(); //主要实现内容
} finally {
// Someone killed this Thread, behave as if Timer cancelled
synchronized(queue) {
newTasksMayBeScheduled = false;
queue.clear(); // Eliminate obsolete references
}
}
}
/**
* The main timer loop. (See class comment.)
*/
private void mainLoop() {
while (true) {
try {
TimerTask task;
boolean taskFired;
synchronized(queue) {
// Wait for queue to become non-empty
//如果队列为空并且是有标志位,则等待。没有标志位的情况为不在需要执行timer了,比如cancel或被gc的时候
while (queue.isEmpty() && newTasksMayBeScheduled)
queue.wait();
if (queue.isEmpty())
break; // Queue is empty and will forever remain; die
// Queue nonempty; look at first evt and do the right thing
long currentTime, executionTime;//分别是当前时间、理论执行时间
task = queue.getMin();//获取就近的task
synchronized(task.lock) {
//如果该task已经被置为cancelled,则将它从队列里面移出
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) { // period表示该task是一次性的,用完就移出
queue.removeMin();//移出task,这块会有queue的重新排序
task.state = TimerTask.EXECUTED;//更新状态为执行中
} else {
//可重复执行的task操作,将重新计算下次执行时间,并重新排序
//重点,此处解释为什么period分正负:区别schedule方法和scheduleAtFixedRate
//如果是负数,则以当前时间为准,往后计算下次执行时间
//如果是正数,则以理论时间为准,往后计算下次执行时间
queue.rescheduleMin(
task.period<0 ? currentTime - task.period
: executionTime + task.period);
}
}
}
if (!taskFired) // 如果还没到任务执行时间就处于等待
queue.wait(executionTime - currentTime);
}
if (taskFired) // 到执行时间了
//执行task中的run方法,而不是start方法,所以并不是另起一个线程进行操作
task.run();
} catch(InterruptedException e) {//如果是不能捕获的异常,就会有风险了
}
}
}
以上是关于java--Timer 定时器的主要内容,如果未能解决你的问题,请参考以下文章