Java定时/延时任务之Timer用法详解
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 的间隔执行
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 的延时执行,看下面的例子:
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
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
2. 第二次在 32s 开始执行第而次,然后往队列立马加入一个 37s 的下一次执行的任务,当前任务执行时间 8s
3. 第三次执行,当 工作线程 执行上一个任务之后已经到 40s 了,由于 40s 已经超过了 37s 的延时任务执行时间,于是会立马开始执行,这时候往队列里面添加一个 42s 执行的任务(下一次执行)
4. 第四次执行,当前任务执行时间 3s,执行完已经 43s 了,这时候执行结束会发现队列里面的 42s 的任务已经过期了,就会往队列立马添加一个 47s 的任务(下一次执行),然后立马开始执行,所以第四次执行时间是 43s
5. 第四次执行的时间是 3s ,执行完任务是 46s,这时候没到 47s,所以没有到下一次任务的执行时间,继续等待到 47s 执行 第五次任务
3.4 固定速率 - public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period)
和上面一样就是选择某个首次执行时间点开始执行,后续速率 period
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)
上面都是周期任务,下面这两个就是非周期任务,顾名思义就是只执行一次的任务,来看例子:
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 时间之后开始执行
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()); } }
以上就是Java定时/延时任务之Timer用法详解的详细内容,更多关于Java Timer用法的资料请关注脚本之家其它相关文章!
最新评论