全面了解Java Timer定时器类
点击上方蓝色字体,选择“标星公众号”
优质文章,第一时间送达
Timer类介绍
Timer类的主要作用是设置计划任务,即在指定时间开始执行某一个任务。Timer主要方法如图:
schedule(TimerTask task, Date time)方法测试
该方法的作用是在指定日期执行一次某一任务。
TimerTask类:
import java.util.TimerTask;
public class MyTask extends TimerTask {
@Override
public void run() {
System.out.println("任务开始了,执行时间为:"+System.currentTimeMillis());
}
}
测试类
import java.util.Date;
import java.util.Timer;
public class testTimer {
public static void main(String[] args) throws InterruptedException{
Long nowTime=System.currentTimeMillis();
System.out.println("当期时间为"+nowTime);
long scheduleTime=nowTime+10000;
System.out.println("开始执行时间:"+scheduleTime);
MyTask myTask = new MyTask();
Timer timer = new Timer();
Thread.sleep(1000);
timer.schedule(myTask,new Date(scheduleTime));
Thread.sleep(Integer.MAX_VALUE);
}
}
10秒之后任务执行完了,但是进程还没有销毁,还在呈红色按钮。说明内部还有线程在执行。为什么会出现这种情况?
查看Timer类的源码可以知道是因为在创建Timer对象时启动了一个新的进程。
private final TimerThread thread = new TimerThread(queue);
public Timer(String name) {
thread.setName(name);
thread.start();
}
TimerThread 是线程类,但是并不是一个守护线程,而且还一直在运行,一直在运行的原因是线程内部有一个死循环,mainLoop()方法,其源码如下:
private void mainLoop() {
while (true) {
try {
TimerTask task;
boolean taskFired;
synchronized(queue) {
// Wait for queue to become non-empty
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();
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 { // Repeating task, reschedule
queue.rescheduleMin(
task.period<0 ? currentTime - task.period
: executionTime + task.period);
}
}
}
if (!taskFired) // Task hasn't yet fired; wait
queue.wait(executionTime - currentTime);
}
if (taskFired) // Task fired; run it, holding no locks
task.run();
} catch(InterruptedException e) {
}
}
private void mainLoop() 方法内部使用while死循环一直执行计划任务,并不退出循环,但是根据源码的执行流程,退出循环的核心代码如下:
while (queue.isEmpty() && newTasksMayBeScheduled)
queue.wait();
if (queue.isEmpty())
break; // Queue is empty and will forever remain; die
下面我们来简单分析一下源代码
1)使用while循环对queue.isEmpty()&&newTasksMayBeScheduled条件进行判断。
2)当&&两端运算结果都为true时,执行wait()方法使当前线程暂停运行,等待被唤醒。
3)唤醒线程的时机是执行了public void schedule(TimerTask task, Date time)方法。
4)唤醒线程后while继续判断queue.isEmpty()&&newTasksMayBeScheduled条件。如果queue.isEmpty()结果为true,则说明队列里并没有任务执行,而且boolean newTasksMayBeScheduled的值由true变成false,继续执行下面的if语句。
5)if (queue.isEmpty())中的结果为true,说明队列为空,那么就执行break语句退出while()死循环。
6)执行public void cancel(),则会使boolean变量newTasksMayBeScheduled的值有true变成false。
7)不执行public void cancel()方法,则变量newTasksMayBeScheduled的值就不会是false,进程就会一直呈死循环的状态,进程不销毁的原因就是因为这个。cancel()方法源代码如下
public void cancel() {
synchronized(queue) {
thread.newTasksMayBeScheduled = false;
queue.clear();
queue.notify(); // In case queue was already empty.
}
}
使用cancel方法来销毁线程
Timer类中的cancel方法作用就是终止此计时器,当前安排的一切任务都不会执行。不过这并不会干扰当前正在执行的任务(如果存在当前任务)。一旦终止了计时器,那么他执行的线程也会终止,并且无法根据他安排更多的任务。
根据上面的源代码分析可知,当队列为空,并且newTasksMayBeScheduled的值是false时退出死循环,导致TimerThread线程结束运行并且销毁。
创建Test2类代码如下:
public class Test2 {
public static void main(String[] args) throws InterruptedException{
Long nowTime=System.currentTimeMillis();
System.out.println("当期时间为"+nowTime);
long scheduleTime=nowTime+15000;
System.out.println("开始执行时间:"+scheduleTime);
MyTask myTask = new MyTask();
Timer timer = new Timer();
timer.schedule(myTask,new Date(scheduleTime));
Thread.sleep(18000);
timer.cancel();
}
}
可见全部线程已经销毁了。
schedule(TimerTask task, Date time)方法的几种情况测试
计划早于当前时间—立即执行当前任务
mytask类
public class MyTask extends TimerTask {
@Override
public void run() {
System.out.println("任务执行了,时间为:"+System.currentTimeMillis());
}
}
//计划早于当前时间---立即执行当前任务
public class Test1 {
public static void main(String[] args) {
long nowTime=System.currentTimeMillis();
System.out.println("当前时间:"+nowTime);
long scheduleTime=(nowTime-5000);
System.out.println("计划时间为:"+scheduleTime);
MyTask myTask = new MyTask();
Timer timer = new Timer();
timer.schedule(myTask,new Date(scheduleTime));
}
}
结果:
当前时间:1616912643987
计划时间为:1616912638987
任务执行了,时间为:1616912643987
schedule方法可以执行多个任务
//执行多个任务
public class Test2 {
public static void main(String[] args){
long nowTime=System.currentTimeMillis();
System.out.println("当前时间:"+nowTime);
long scheduleTime=(nowTime+5000);
long scheduleTime2=(nowTime+8000);
System.out.println("计划时间为:"+scheduleTime);
System.out.println("计划时间为:"+scheduleTime2);
MyTask myTask = new MyTask();
MyTask myTask2 = new MyTask();
Timer timer = new Timer();
timer.schedule(myTask,new Date(scheduleTime));
timer.schedule(myTask2,new Date(scheduleTime2));
}
}
结果:
当前时间:1616912808406
计划时间为:1616912813406
计划时间为:1616912816406
任务执行了,时间为:1616912813417
任务执行了,时间为:1616912816407
延时执行TimerTask
TimerTask以队列的方式逐一按顺序执行,所以执行的时间可能与预期时间并不一致,因为前面任务可能消耗的时间较长,后面的任务运行时间可能被延后。
public class MyTaskA extends TimerTask {
@Override
public void run() {
try {
System.out.println("A begin time=" + System.currentTimeMillis());
Thread.sleep(10000);
System.out.println("A end time=" + System.currentTimeMillis());
}catch (Exception e){
e.printStackTrace();
}
}
}
public class MyTaskB extends TimerTask {
@Override
public void run() {
try {
System.out.println("B begin time=" + System.currentTimeMillis());
Thread.sleep(10000);
System.out.println("B end time=" + System.currentTimeMillis());
}catch (Exception e){
e.printStackTrace();
}
}
}
测试类:
public class Test3 {
public static void main(String[] args) {
long nowTime=System.currentTimeMillis();
System.out.println("当前时间:"+nowTime);
long scheduleTime=nowTime;
long scheduleTime2=(nowTime+5000);
System.out.println("计划时间为:"+scheduleTime);
System.out.println("计划时间为:"+scheduleTime2);
MyTaskA myTaskA = new MyTaskA();
MyTaskB myTaskB = new MyTaskB();
Timer timer = new Timer();
timer.schedule(myTaskA,new Date(scheduleTime));
timer.schedule(myTaskB,new Date(scheduleTime2));
}
}
结果:
当前时间:1616913423195
计划时间为:1616913423195
计划时间为:1616913428195
A begin time=1616913423195
A end time=1616913433196
B begin time=1616913433196
B end time=1616913443211
计划B原本执行的时间是long scheduleTime2=(nowTime+5000);与计划A运行间隔时间为5秒,但是因为A任务运行需要10秒,所有任务A执行结束的时间就是任务B开始的时间。
schedule(TimerTask task, Date firstTime, long period)方法测试
计划时间晚于当前时间————在未来执行的效果
该方法的作用是在指定日期之后按照指定的时间间隔无限的执行某一个任务。
public class Test3 {
public static void main(String[] args) {
long nowTime=System.currentTimeMillis();
System.out.println("当前时间:"+nowTime);
long scheduleTime=(nowTime+10000);
System.out.println("计划时间为:"+scheduleTime);
MyTask myTask = new MyTask();
Timer timer = new Timer();
timer.schedule(myTask,new Date(scheduleTime),4000);
}
}
结果:
计划时间为:1616914137464
任务执行了,时间为:1616914137474
任务执行了,时间为:1616914141494
任务执行了,时间为:1616914145514
任务执行了,时间为:1616914149534
从程序的运行结果来看每隔4秒运行一次,并且无限重复的执行该任务。
计划时间早于当前时间————立即执行的效果
public class Test3 {
public static void main(String[] args) {
long nowTime=System.currentTimeMillis();
System.out.println("当前时间:"+nowTime);
long scheduleTime=(nowTime-10000);
System.out.println("计划时间为:"+scheduleTime);
MyTask myTask = new MyTask();
Timer timer = new Timer();
timer.schedule(myTask,new Date(scheduleTime),4000);
}
}
结果:
当前时间:1616914545243
计划时间为:1616914535243
任务执行了,时间为:1616914545243
任务执行了,时间为:1616914549253
任务执行了,时间为:1616914553254
任务执行了,时间为:1616914557264
任务执行了,时间为:1616914561283
延时执行TimerTask任务
public class Test3 {
public static void main(String[] args) {
long nowTime=System.currentTimeMillis();
System.out.println("当前时间:"+nowTime);
long scheduleTime=nowTime;
System.out.println("计划时间为:"+scheduleTime);
MyTaskA myTaskA = new MyTaskA();
Timer timer = new Timer();
timer.schedule(myTaskA,new Date(scheduleTime),4000);
}
}
结果:
当前时间:1616914846353
计划时间为:1616914846353
A begin time=1616914846353
A end time=1616914851363
A begin time=1616914851363
A end time=1616914856373
A begin time=1616914856373
A end time=1616914861383
代码schedule(myTaskA,new Date(scheduleTime),4000);本来计划每个任务执行时间为4秒但是任务A执行了Thread.sleep(5000)要5秒。
TimerTask类中的cancel()方法和Timer中的cancel()方法的区别
TimerTask类中的cancel()方法
TimerTask类中的cancel()方法作用是将自己在任务队列中清除。该方法的源代码如下
public boolean cancel() {
synchronized(lock) {
boolean result = (state == SCHEDULED);
state = CANCELLED;
return result;
}
}
从方法的源代码可以分析出执行TimerTask中的该方法,当前任务TimerTask任务的state状态将会变成CANCELLED。
public class MyThreadA extends TimerTask {
@Override
public void run() {
System.out.println("A run time="+System.currentTimeMillis());
this.cancel();
System.out.println("A任务将自己移除");
}
}
class MyThreadB extends TimerTask{
@Override
public void run() {
System.out.println("B run time="+System.currentTimeMillis());
}
}
class test{
public static void main(String[] args) {
long nowTime=System.currentTimeMillis();
System.out.println("当前时间:"+nowTime);
System.out.println("计划时间为:"+nowTime);
MyThreadB myThreadB = new MyThreadB();
MyThreadA myThreadA = new MyThreadA();
Timer timer = new Timer();
timer.schedule(myThreadA,new Date(nowTime),3000);
timer.schedule(myThreadB,new Date(nowTime),3000);
}
结果:
当前时间:1616916022030
计划时间为:1616916022030
A run time=1616916022035
A任务将自己移除
B run time=1616916022035
B run time=1616916025041
B run time=1616916028061
Timer类中的cancel()方法
Timer类中的cancel()方法作用是将任务队列中所有任务清除。该方法的源代码如下
public void cancel() {
synchronized(queue) {
thread.newTasksMayBeScheduled = false;
queue.clear();
queue.notify(); // In case queue was already empty.
}
}
public class MyThreadA extends TimerTask {
@Override
public void run() {
System.out.println("A run time="+System.currentTimeMillis());
}
}
class MyThreadB extends TimerTask{
@Override
public void run() {
System.out.println("B run time="+System.currentTimeMillis());
}
}
class test{
public static void main(String[] args) throws Exception{
long nowTime=System.currentTimeMillis();
System.out.println("当前时间:"+nowTime);
System.out.println("计划时间为:"+nowTime);
MyThreadB myThreadB = new MyThreadB();
MyThreadA myThreadA = new MyThreadA();
Timer timer = new Timer();
timer.schedule(myThreadA,new Date(nowTime),4000);
timer.schedule(myThreadB,new Date(nowTime),4000);
Thread.sleep(10000);
timer.cancel();
System.out.println("任务全部取消了");
}
}
结果:
当前时间:1616916352810
计划时间为:1616916352810
A run time=1616916352810
B run time=1616916352810
B run time=1616916356818
A run time=1616916356818
A run time=1616916360820
B run time=1616916360820
任务全部取消了
Timer类中的cancel方法有时候不一定会停止计划任务,即任务正常执行。
其原因是没有抢到queue锁,导致TimerTask中的任务正常执行。
public class MyTask extends TimerTask {
private int i;
public MyTask(int i) {
this.i = i;
}
@Override
public void run() {
System.out.println("第"+i+"次没有被取消");
}
}
class tests{
public static void main(String[] args) {
int i=0;
long nowTime=System.currentTimeMillis();
System.out.println("当前时间:"+nowTime);
System.out.println("计划时间为:"+nowTime);
while (true){
i++;
Timer timer = new Timer();
MyTask myTask = new MyTask(i);
timer.schedule(myTask,new Date(nowTime));
timer.cancel();
}
}
}
结果:
当前时间:1616921790699
计划时间为:1616921790699
第123次没有被取消
第12802次没有被取消
间隔执行Task任务的算法
当队列中有3个任务ABC时这三个任务执行的顺序的算法是每次将最后一个任务放入队头,再执行队头中的方法。其执行顺序效果如下:
1)ABC;
2)CAB;将C放入AB之前
3)BCA;将B放入CA之前
验证如下:
public class MyThreadA extends TimerTask {
@Override
public void run() {
System.out.println("A run time="+System.currentTimeMillis());
}
}
class MyThreadB extends TimerTask{
@Override
public void run() {
System.out.println("B run time="+System.currentTimeMillis());
}
}
class MyThreadC extends TimerTask{
@Override
public void run() {
System.out.println("C run time="+System.currentTimeMillis());
}
}
class test{
public static void main(String[] args) throws Exception{
long nowTime=System.currentTimeMillis();
System.out.println("当前时间:"+nowTime);
System.out.println("计划时间为:"+nowTime);
MyThreadA myThreadA = new MyThreadA();
MyThreadB myThreadB = new MyThreadB();
MyThreadC myThreadC = new MyThreadC();
Timer timer = new Timer();
timer.schedule(myThreadA,new Date(nowTime),2000);
timer.schedule(myThreadB,new Date(nowTime),2000);
timer.schedule(myThreadC,new Date(nowTime),2000);
Thread.sleep(Integer.MAX_VALUE);
}
}
测试结果:
当前时间:1616917048998
计划时间为:1616917048998
A run time=1616917049005
B run time=1616917049005
C run time=1616917049005
C run time=1616917051005
A run time=1616917051005
B run time=1616917051005
B run time=1616917053006
C run time=1616917053006
A run time=1616917053006
schedule(TimerTask task, long delay)
schedule(TimerTask task, long delay) 该方法的作用是以当前的时间为参考时间在此时间上加上指定的延迟时间。
public class Run {
static public class myTask extends TimerTask{
@Override
public void run() {
System.out.println("开始运行了,时间为:"+System.currentTimeMillis());
}
}
public static void main(String[] args) {
myTask myTask = new myTask();
Timer timer = new Timer();
System.out.println("当前时间:"+System.currentTimeMillis());
timer.schedule(myTask,5000);
}
}
结果:
当前时间:1616922170729
开始运行了,时间为:1616922175739
运行时间被延迟了5秒。
schedule(TimerTask task, long delay, long period)
schedule(TimerTask task, long delay, long period)该方法的作用是以当前的时间为参考时间在此时间上加上指定的延迟时间,在以此时间间隔上无限的执行某一任务。
public class Run {
static public class myTask extends TimerTask{
@Override
public void run() {
System.out.println("开始运行了,时间为:"+System.currentTimeMillis());
}
}
public static void main(String[] args) {
myTask myTask = new myTask();
Timer timer = new Timer();
System.out.println("当前时间:"+System.currentTimeMillis());
timer.schedule(myTask,5000,2000);
}
}
结果:
当前时间:1616922425863
开始运行了,时间为:1616922430879
开始运行了,时间为:1616922432879
开始运行了,时间为:1616922434899
开始运行了,时间为:1616922436909
scheduleAtFixedRate方法
scheduleAtFixedRate(TimerTask task, long delay, long period)该方法与 schedule(TimerTask task, long delay, long period) 这两个方法的用法大致是一样的。
scheduleAtFixedRate(TimerTask task, Date firstTime, long period)该方法与 schedule(TimerTask task, Date firstTime, long period) 这两个方法的用法大致是一样的。
但是他们之间也是有区别的。他们之间的区别就是是否具有追赶性。下面我们用两个案例来测试他们的追赶性。
验证schedule()方法 不具有追赶性
public class Test6 {
static public class myTask extends TimerTask {
@Override
public void run() {
System.out.println("begin time:"+System.currentTimeMillis());
System.out.println("end time:"+System.currentTimeMillis());
}
}
public static void main(String[] args) {
myTask myTask = new myTask();
Timer timer = new Timer();
long nowTime=System.currentTimeMillis();
System.out.println("当前时间:"+nowTime);
long runTime=nowTime-10000;
System.out.println("现在计划执行时间:"+runTime);
timer.schedule(myTask,new Date(runTime),2000);
}
}
结果;
当前时间:1616923764405
现在计划执行时间:1616923754405
begin time:1616923764405
end time:1616923764405
begin time:1616923766406
end time:1616923766406
begin time:1616923768423
end time:1616923768423
begin time:1616923770426
end time:1616923770426
begin time:1616923772446
end time:1616923772446
从结果来看1616923754405时间到1616923764405这一时间的任务都没有被执行,这说明schedule()方法不具有追赶性。
验证scheduleAtFixedRate()方法具有追赶性
public class Test6 {
static public class myTask extends TimerTask {
@Override
public void run() {
System.out.println("begin time:"+System.currentTimeMillis());
System.out.println("end time:"+System.currentTimeMillis());
}
}
public static void main(String[] args) {
myTask myTask = new myTask();
Timer timer = new Timer();
long nowTime=System.currentTimeMillis();
System.out.println("当前时间:"+nowTime);
long runTime=nowTime-10000;
System.out.println("现在计划执行时间:"+runTime);
timer.scheduleAtFixedRate(myTask,new Date(runTime),2000);
}
}
结果:
当前时间:1616924118955
现在计划执行时间:1616924108955
begin time:1616924118955
end time:1616924118955
begin time:1616924118955
end time:1616924118955
begin time:1616924118955
end time:1616924118955
begin time:1616924118955
end time:1616924118955
begin time:1616924118955
end time:1616924118955
begin time:1616924118955
end time:1616924118955
begin time:1616924120956
end time:1616924120956
begin time:1616924122955
end time:1616924122955
begin time:1616924124956
end time:1616924124956
begin time:1616924126955
end time:1616924126955
输出时间1616924118955都是曾经流逝时间的任务追赶,也就是将之前任务没有执行的任务追加执行,将这10秒之内的任务次数输出完,再每间隔2秒执行一次任务。将这两段时间所对应的任务弥补执行,也就是在指定时间段内的运行次数必须运行完整,这就是Task任务的追赶性。
————————————————
版权声明:本文为CSDN博主「程序员小汪」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:
https://blog.csdn.net/weixin_44991304/article/details/115256074
锋哥最新SpringCloud分布式电商秒杀课程发布
👇👇👇
👆长按上方微信二维码 2 秒
感谢点赞支持下哈