文章目录
- [1. 概要](#1. 概要)
- [2. 简述](#2. 简述)
-
- [2.1 固定速率](#2.1 固定速率)
- [2.2 固定延时](#2.2 固定延时)
- [2.3 区别](#2.3 区别)
- [3. Timer 的用法](#3. Timer 的用法)
-
- [3.1 固定延时 - public void schedule(TimerTask task, long delay, long period)](#3.1 固定延时 - public void schedule(TimerTask task, long delay, long period))
-
- [3.1.1 解释](#3.1.1 解释)
- [3.2 固定延时 - public void schedule(TimerTask task, Date firstTime, long period)](#3.2 固定延时 - public void schedule(TimerTask task, Date firstTime, long period))
- [3.3 固定速率 - public void scheduleAtFixedRate(TimerTask task, long delay, long period)](#3.3 固定速率 - public void scheduleAtFixedRate(TimerTask task, long delay, long period))
-
- [3.3.1 解释](#3.3.1 解释)
- [3.4 固定速率 - public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period)](#3.4 固定速率 - public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period))
- [3.5 非周期任务 - public void schedule(TimerTask task, Date time)](#3.5 非周期任务 - public void schedule(TimerTask task, Date time))
- [3.6 非周期任务 - schedule(TimerTask task, long delay)](#3.6 非周期任务 - schedule(TimerTask task, long delay))
- [4. 小结](#4. 小结)
1. 概要
上一篇文章地址:定时/延时任务-自己实现一个简单的定时器
在上一篇文章中,我们自己实现了一个简单的 Timer 并提出了一些缺点,下面我们就来看看 JDK 中的 Timer 用法
2. 简述
在 Java Development Kit (JDK) 中,java.util.Timer 是一个用于调度任务的工具类。Timer 类使用一个后台线程来遍历队列中的任务,同时可以按照固定的延时或者固定的速率来重复执行。
首先在介绍 Timer 的 API 之前,先来看两个概念:
2.1 固定速率
固定速率 策略表示任务在固定的时间间隔内重复执行,不管任务的执行时间有多长,如果任务的执行时间超过了时间间隔,那么下一个任务会在当前任务执行完毕之后就会马上开始执行 ,下面是一个例子:假设我们设置了一个固定速率为 5
的任务,从 0s
开始执行,也就是说这个任务 5s
执行一次:
- 第一次执行: 在
0s
开始执行一次,假设执行时间是3s
- 第二次执行: 在
5s
开始执行第二次,假设执行的时候被阻塞了,执行了8s
- 第三次执行: 在
13s
开始执行第三次,假设执行的时候被阻塞了,执行了3s
- 第四次执行: 在
16s
开始执行第四次,假设执行的时候没有被阻塞 - 第四次执行: 在
20s
开始执行第五次,假设执行的时候没有被阻塞 - ...
看了上面的过程分析,你可能有点懵,没关系,等到下面的时候会有例子并解释
2.2 固定延时
固定延时 策略表示任务在当前任务执行完成之后,固定延时一段时间再执行下一个任务 ,下面是一个例子:假设我们设置了一个固定速率为 5
的任务,从 0s
开始执行,也就是说这个任务 5s
执行一次:
- 第一次执行: 在
0s
开始执行一次,假设执行时间是3s
- 第二次执行: 在
5s
开始执行第二次,假设执行的时候被阻塞了,执行了8s
- 第三次执行: 在
13s
开始执行第三次,假设执行的时候被阻塞了,执行了3s
- 第四次执行: 在
18s
开始执行第四次,假设执行的时候没有被阻塞 - 第四次执行: 在
23s
开始执行第五次,假设执行的时候没有被阻塞 - ...
2.3 区别
看了上面两种方式的分析,可以做一个小的总结:
- 固定速率: 任务会按照固定时间间隔执行,如果任务执行的时间大于时间间隔,那么下一个任务会马上执行
- 固定延时: 任务会按照固定延时执行,如果任务执行的时间小于时间间隔,那么两次任务的执行时间间隔就是设置的延时;如果任务执行的时间大于时间间隔,那么两次任务执行的时间间隔就是任务执行的时间,也就是说下一次任务会马上执行
- 固定速率 这种方式比适合用于严格要求按照时间间隔执行的任务,比如心跳探测、数据收集等...
- 固定延时 执行任务的时间不固定,但是得确保每一次任务执行完之后有一定时间间隔再执行下一次的任务,比如日志收集、数据清理等...
3. Timer 的用法
下面我们就来介绍下 Timer 的几个 API 的用法
3.1 固定延时 - public void schedule(TimerTask task, long delay, long period)
这个 API
的意思是:延时 delay 后开始按照 period 的间隔执行
java
public class Pra {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
try {
System.out.println("Thread-Current: " + Thread.currentThread().getName() + ", time = " + getTime());
if(Math.random() < 0.5){
System.out.println("sleep: 3s");
Thread.sleep(3000);
} else {
System.out.println("sleep: 8s");
Thread.sleep(8000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, 0, 5000);
}
private static String getTime() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
return sdf.format(new Date());
}
}
输出结果如下:
3.1.1 解释
解释: Timer
源码中对于任务的添加是线程被唤醒后获取到任务,之后就会立马计算出下一次该任务的调度时间加入队列中
有了上面的基础再来看输出,这就是为什么任务执行了 3s
最终还是在 49s
就开始执行下一个任务,因为 44s
执行任务的时候会根据当前执行时间算出下一个任务执行时间应该是 44 + 5 = 49s
,而第二个任务 49s
执行的时候会算出下一个任务执行时间是 49 + 5 = 54s
,但是由于任务执行时间长达 8s
,导致下一个任务根本没时间被调度,所以只能在 57s
执行完之后去看队列,发现队列里面 54s
的任务早就到时间了,这时候算出下一个任务的执行时间是 57 + 5 = 02s
,于是把这个任务加入到队列中,然后立马调度这个 54s
的任务,以此类推
你可能会有疑问:如果我任务执行时间是 8s,任务间隔是 3s,不会导致执行完一个任务之后队列中会有多个没有执行的任务吗?
并不会,因为固定延时是按照当前时间来算下一个任务的计算时间,所以任务执行时间大于任务间隔时间的前提下,不管你间隔多少,都是以任务执行时间为主
但是对于固定速率又不一样了,这个我们下面会说
3.2 固定延时 - public void schedule(TimerTask task, Date firstTime, long period)
顾名思义,就是设置一个第一次启动的时间点,然后以 period 的延时执行,看下面的例子:
java
public class Pra {
public static void main(String[] args) {
Timer timer = new Timer();
System.out.println("start time = " + getTime());
timer.schedule(new TimerTask() {
@Override
public void run() {
try {
System.out.println("Thread-Current: " + Thread.currentThread().getName() + ", time = " + getTime());
if(Math.random() < 0.5){
System.out.println("sleep: 3s");
Thread.sleep(3000);
} else {
System.out.println("sleep: 8s");
Thread.sleep(8000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, new Date(System.currentTimeMillis() + 10000), 5000);
}
private static String getTime() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
return sdf.format(new Date());
}
}
上面 3.1 已经有解释了
3.3 固定速率 - public void scheduleAtFixedRate(TimerTask task, long delay, long period)
当前延时 delay 开始,接着每次执行的间隔是 period
java
public class Pra {
public static void main(String[] args) {
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
try {
System.out.println("Thread-Current: " + Thread.currentThread().getName() + ", time = " + getTime());
if(Math.random() < 0.5){
System.out.println("sleep: 3s");
Thread.sleep(3000);
} else {
System.out.println("sleep: 8s");
Thread.sleep(8000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, 0, 5000);
}
private static String getTime() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
return sdf.format(new Date());
}
}
3.3.1 解释
1.首先
在 27s
开始执行第一次,然后往队列立马加入一个 32s
的下一次执行的任务,当前任务执行时间 3s
-
第二次
在32s
开始执行第而次,然后往队列立马加入一个37s
的下一次执行的任务,当前任务执行时间8s
-
第三次执行
,当工作线程
执行上一个任务之后已经到40s
了,由于40s
已经超过了37s
的延时任务执行时间,于是会立马开始执行,这时候往队列里面添加一个42s
执行的任务(下一次执行) -
第四次执行
,当前任务执行时间3s
,执行完已经43s
了,这时候执行结束会发现队列里面的42s
的任务已经过期了,就会往队列立马添加一个47s
的任务(下一次执行),然后立马开始执行,所以第四次执行时间是43s
-
第四次执行的时间是
3s
,执行完任务是46s
,这时候没到47s
,所以没有到下一次任务的执行时间,继续等待到47s
执行第五次任务
3.4 固定速率 - public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period)
和上面一样就是选择某个首次执行时间点开始执行,后续速率 period
java
public class Pra {
public static void main(String[] args) {
Timer timer = new Timer();
System.out.println("start time = " + getTime());
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
try {
System.out.println("Thread-Current: " + Thread.currentThread().getName() + ", time = " + getTime());
if(Math.random() < 0.5){
System.out.println("sleep: 3s");
Thread.sleep(3000);
} else {
System.out.println("sleep: 8s");
Thread.sleep(8000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, new Date(), 5000);
}
private static String getTime() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
return sdf.format(new Date());
}
}
3.5 非周期任务 - public void schedule(TimerTask task, Date time)
上面都是周期任务,下面这两个就是非周期任务,顾名思义就是只执行一次的任务,来看例子:
java
public class Pra {
public static void main(String[] args) {
Timer timer = new Timer();
System.out.println("start time = " + getTime());
timer.schedule(new TimerTask() {
@Override
public void run() {
try {
System.out.println("Thread-Current: " + Thread.currentThread().getName() + ", time = " + getTime());
if(Math.random() < 0.5){
System.out.println("sleep: 3s");
Thread.sleep(3000);
} else {
System.out.println("sleep: 8s");
Thread.sleep(8000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, new Date(System.currentTimeMillis() + 10000));
}
private static String getTime() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
return sdf.format(new Date());
}
}
3.6 非周期任务 - schedule(TimerTask task, long delay)
延迟 delay
时间之后开始执行
java
public class Pra {
public static void main(String[] args) {
Timer timer = new Timer();
System.out.println("start time = " + getTime());
timer.schedule(new TimerTask() {
@Override
public void run() {
try {
System.out.println("Thread-Current: " + Thread.currentThread().getName() + ", time = " + getTime());
if(Math.random() < 0.5){
System.out.println("sleep: 3s");
Thread.sleep(3000);
} else {
System.out.println("sleep: 8s");
Thread.sleep(8000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, 10000);
}
private static String getTime() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
return sdf.format(new Date());
}
}
4. 小结
这篇文章中我们详细介绍了 Timer 的用法,下一篇文章就来安排上源码的详细解析
如有错误,欢迎指出!!!