Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱
MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina.com

线程 Timer TimerTask 计时器 定时任务


目录

Timer 计时器

Timer可以定时(在指定时间执行任务)、延迟(延迟指定时间执行任务)、周期性地执行任务(每隔指定时间执行一次任务)。

一种工具,线程用其安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行。

与每个 Timer 对象相对应的是单个后台线程,用于顺序地执行所有计时器任务。计时器任务应该迅速完成。如果完成某个计时器任务的时间太长,那么它会“独占”计时器的任务执行线程。因此,这就可能延迟后续任务的执行,而这些任务就可能“堆在一起”,并且在上述不友好的任务最终完成时才能够被快速连续地执行。

对 Timer 对象最后的引用完成后,并且 所有未处理的任务都已执行完成后,计时器的任务执行线程会正常终止(并且成为垃圾回收的对象)。但是这可能要很长时间后才发生。

默认情况下,任务执行线程并不作为守护线程来运行,所以它能够阻止应用程序终止。如果调用者想要快速终止计时器的任务执行线程,那么调用者应该调用计时器的 cancel 方法。

如果意外终止了计时器的任务执行线程,例如调用了它的 stop 方法,那么所有以后对该计时器安排任务的尝试都将导致 IllegalStateException,就好像调用了计时器的 cancel 方法一样。

此类是线程安全的:多个线程可以共享单个 Timer 对象而无需进行外部同步。

此类不提供实时保证:它使用 Object.wait(long) 方法来安排任务。

实现注意事项:此类可扩展到大量同时安排的任务(存在数千个都没有问题)。在内部,它使用二进制堆来表示其任务队列,所以安排任务的开销是 O(log n),其中 n 是同时安排的任务数。

实现注意事项:所有构造方法都启动计时器线程。

构造方法

  • Timer() 创建一个新计时器。
  • Timer(String name) 创建一个新计时器,其相关的线程具有指定的名称。
  • Timer(boolean isDaemon) 创建一个新计时器,可以指定其相关的线程作为守护程序运行。
  • Timer(String name, boolean isDaemon) 创建一个新计时器,其相关的线程具有指定的名称,并且可以指定作为守护程序运行。

公共方法

  • void cancel() 终止此计时器,丢弃所有当前已安排的任务。

    • 这不会干扰当前正在执行的任务(如果存在)。
    • 一旦终止了计时器,那么它的执行线程也会终止,并且无法根据它安排更多的任务。
    • 注意,在此计时器调用的计时器任务的 run 方法内调用此方法,就可以绝对确保正在执行的任务是此计时器所执行的最后一个任务。
    • 可以重复调用此方法,但是第二次和后续调用无效。
  • int purge() 从此计时器的任务队列中移除所有已取消的任务。
  • void schedule(TimerTask task, long delay) t安排在指定延迟后执行指定的任务。ask - 所要安排的任务,delay - 执行任务前的延迟时间(毫秒)。
  • void schedule(TimerTask task, Date time) 安排在指定的时间执行指定的任务。如果此时间已过去,则安排立即执行该任务。
  • void schedule(TimerTask task, long delay, long period) 安排指定的任务从指定的延迟后开始进行重复的固定延迟执行。以近似固定的时间间隔(由指定的周期分隔)进行后续执行。
    • 在固定延迟执行中,根据前一次执行的实际执行时间来安排每次执行。如果由于任何原因(如垃圾回收或其他后台活动)而延迟了某次执行,则后续执行也将被延迟。从长期来看,执行的频率一般要稍慢于指定周期的倒数(假定 Object.wait(long) 所依靠的系统时钟是准确的)。
    • 固定延迟执行适用于那些需要“平稳”运行的重复活动。换句话说,它适用于在短期运行中保持频率准确要比在长期运行中更为重要的活动。这包括大多数动画任务,如以固定时间间隔闪烁的光标。这还包括为响应人类活动所执行的固定活动,如在按住键时自动重复输入字符。
  • void schedule(TimerTask task, Date firstTime, long period) 安排指定的任务在指定的时间开始进行重复的固定延迟执行。
  • void scheduleAtFixedRate(TimerTask task, long delay, long period) 安排指定的任务在指定的延迟后开始进行重复的固定速率执行。以近似固定的时间间隔(由指定的周期分隔)进行后续执行。
    • 在固定速率执行中,根据已安排的初始执行时间来安排每次执行。如果由于任何原因(如垃圾回收或其他后台活动)而延迟了某次执行,则将快速连续地出现两次或更多的执行,从而使后续执行能够“追赶上来”。从长远来看,执行的频率将正好是指定周期的倒数(假定 Object.wait(long) 所依靠的系统时钟是准确的)。
    • 固定速率执行适用于那些对绝对时间敏感的重复执行活动,如每小时准点打钟报时,或者在每天的特定时间运行已安排的维护活动。它还适用于那些完成固定次数执行的总计时间很重要的重复活动,如倒计时的计时器,每秒钟滴答一次,共 10 秒钟。最后,固定速率执行适用于安排多个重复执行的计时器任务,这些任务相互之间必须保持同步。
  • void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) 安排指定的任务在指定的时间开始进行重复的固定速率执行。

TimerTask 计时任务

所有已实现的接口:Runnable

由 Timer 安排为一次执行或重复执行的任务。

构造方法

protected TimerTask() 创建一个新的计时器任务。

公共方法

  • boolean cancel() 取消此计时器任务。

    • 如果任务安排为一次执行且还未运行,或者尚未安排,则永远不会运行。如果任务安排为重复执行,则永远不会再运行。(如果发生此调用时任务正在运行,则任务将运行完,但永远不会再运行。)
    • 注意,从重复的计时器任务的 run 方法中调用此方法绝对保证计时器任务不会再运行。
    • 此方法可以反复调用;第二次和以后的调用无效。
    • 返回:如果此任务安排为一次执行且尚未运行,或者此任务安排为重复执行,则返回 true。如果此任务安排为一次执行且已经运行,或者此任务尚未安排,或者此任务已经取消,则返回 false。(一般来说,如果此方法不允许发生一个或多个已安排执行,则返回 true。)
  • abstract void run() 此计时器任务要执行的操作。
  • long scheduledExecutionTime() 返回此任务最近实际执行的已安排执行时间。

案例

案例1:延时执行指定任务

final SimpleDateFormat format = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss SSS", Locale.getDefault());
System.out.println(format.format(new Date()));
new Timer().schedule(new TimerTask() {
@Override
public void run() {
System.out.println(format.format(new Date()));
}
}, 500);

案例2:执行定时任务

每10毫秒执行一次指定任务

1、使用 schedule 方式:

final SimpleDateFormat format = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss SSS", Locale.getDefault());
System.out.println("schedule 开始时间 " + format.format(new Date()));
new Timer().schedule(new TimerTask() {//或用 scheduleAtFixedRate 方法
@Override
public void run() {
System.out.println("run 循环执行时间 " + format.format(new Date()));
}
}, 0, 10);

打印结果:

schedule 开始时间 2018.06.12 09:27:31 186
run 循环执行时间 2018.06.12 09:27:31 189
run 循环执行时间 2018.06.12 09:27:31 200
run 循环执行时间 2018.06.12 09:27:31 211
run 循环执行时间 2018.06.12 09:27:31 222
run 循环执行时间 2018.06.12 09:27:31 233
run 循环执行时间 2018.06.12 09:27:31 243
run 循环执行时间 2018.06.12 09:27:31 254
run 循环执行时间 2018.06.12 09:27:31 265
run 循环执行时间 2018.06.12 09:27:31 276
run 循环执行时间 2018.06.12 09:27:31 287
run 循环执行时间 2018.06.12 09:27:31 297
run 循环执行时间 2018.06.12 09:27:31 308
run 循环执行时间 2018.06.12 09:27:31 318
run 循环执行时间 2018.06.12 09:27:31 329

可以发现,后续执行时间和首次执行时间的误差是越来越大的,如果时间非常长的话,误差会非常明显,所以schedule适合短时间的周期任务。

2、使用 scheduleAtFixedRate 方式:

打印结果:

schedule 开始时间 2018.06.12 09:29:53 681
run 循环执行时间 2018.06.12 09:29:53 683
run 循环执行时间 2018.06.12 09:29:53 694
run 循环执行时间 2018.06.12 09:29:53 704
run 循环执行时间 2018.06.12 09:29:53 714
run 循环执行时间 2018.06.12 09:29:53 724
run 循环执行时间 2018.06.12 09:29:53 734
run 循环执行时间 2018.06.12 09:29:53 744
run 循环执行时间 2018.06.12 09:29:53 754
run 循环执行时间 2018.06.12 09:29:53 764
run 循环执行时间 2018.06.12 09:29:53 775
run 循环执行时间 2018.06.12 09:29:53 784
run 循环执行时间 2018.06.12 09:29:53 794
run 循环执行时间 2018.06.12 09:29:53 804

可以发现,后续执行时间和首次执行时间的误差基本是不变的,即时间非常长时误差也很小,所以schedule适合长短时间的周期任务。

Timer 的缺陷

Timer 抛出异常缺陷

Timer线程是不会捕获异常的,如果TimerTask抛出的了未检查异常则会导致Timer线程终止,同时Timer也不会重新恢复线程的执行,他会错误的认为整个Timer线程都会取消。同时,已经被安排单尚未执行的TimerTask也不会再执行了,新的任务也不能被调度。故如果TimerTask抛出未检查的异常,Timer将会产生无法预料的行为(当然,可以手动 try catch 捕获此异常)。

下例中TimerTask抛出了未检查的RuntimeException,之后Timer会终止所有任务的运行,该Timer线程也会被立即终止:

Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("执行第一个TimerTask " + System.currentTimeMillis());
throw new RuntimeException();//这里抛出未检测的RuntimeException之后,整个Timer线程会被终止
}
}, 0);

Thread.sleep(100);

System.out.println("准备执行第二个TimerTask " + System.currentTimeMillis());
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("执行第二个TimerTask " + System.currentTimeMillis());
}
}, 0);

打印结果:

执行第一个TimerTask  1528768752890
Exception in thread "Timer-0" java.lang.RuntimeException
at Test$1.run(Test.java:11)
at java.util.TimerThread.mainLoop(Unknown Source)
at java.util.TimerThread.run(Unknown Source)
准备执行第二个TimerTask 1528768752991
Exception in thread "main" java.lang.IllegalStateException: Timer already cancelled.
at java.util.Timer.sched(Unknown Source)
at java.util.Timer.schedule(Unknown Source)
at Test.main(Test.java:18)

可以发现,执行第一个TimerTask时出现未捕获的异常后,第二个TimerTask就不会执行了,因为整个 Timer already cancelled 。

Timer 管理时间延迟缺陷

Timer在执行所有定时任务时只会创建一个线程,在执行周期性任务时,如果某个周期性任务的执行时间长度大于其周期时间长度,那么就会导致这一次的任务还在执行,而下一个周期的任务已经需要开始执行了,这样会导致越来越严重的时间延迟。

private int num = 0;

public void test() {
final SimpleDateFormat format = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss SSS", Locale.getDefault());
new Timer().schedule(new TimerTask() { //换成 scheduleAtFixedRate 时效果基本一致
@Override
public void run() {
System.out.println(num++ + " " + format.format(new Date()));
try {
Thread.sleep(2000);//任务执行时间大于间隔时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, 0, 1000);
}

打印结果:

0  2018.06.12 11:05:29 517
1 2018.06.12 11:05:31 517
2 2018.06.12 11:05:33 518
3 2018.06.12 11:05:35 519
4 2018.06.12 11:05:37 520
5 2018.06.12 11:05:39 520
6 2018.06.12 11:05:41 521
7 2018.06.12 11:05:43 522
8 2018.06.12 11:05:45 523
9 2018.06.12 11:05:47 524

同样,如果存在多个任务,若其中某个任务执行时间过长, 会导致其他的任务的实效准确性出现问题。

final SimpleDateFormat format = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss SSS", Locale.getDefault());
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("执行第一个任务 " + format.format(new Date()));
try {
Thread.sleep(2000);//第一个任务执行时间过长
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, 0);

timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("执行第二个任务 " + format.format(new Date()));//导致第二个任务的执行时间也受到影响
}
}, 0);

打印结果:

执行第一个任务  2018.06.12 11:14:18 352
执行第二个任务 2018.06.12 11:14:20 353

用 ScheduledExecutorService 替代 Timer

可以规避 Timer 抛出异常缺陷:

ScheduledExecutorService scheduExec = Executors.newScheduledThreadPool(2);

scheduExec.schedule(() -> {
System.out.println("执行第一个任务 " + System.currentTimeMillis());
throw new RuntimeException();//这里抛出未检测的RuntimeException
}, 0, TimeUnit.MILLISECONDS);

Thread.sleep(100);

scheduExec.schedule(() -> {
System.out.println("执行第二个任务 " + System.currentTimeMillis());//第二个任务不会受到影响
}, 0, TimeUnit.MILLISECONDS);

打印结果:

执行第一个TimerTask  1528771566751
执行第二个TimerTask 1528771566852

可以规避 Timer 管理时间延迟缺陷:

final SimpleDateFormat format = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss SSS", Locale.getDefault());
ScheduledExecutorService scheduExec = Executors.newScheduledThreadPool(2);

scheduExec.schedule(() -> {
System.out.println("执行第一个任务 " + format.format(new Date()));
try {
Thread.sleep(2000);//第一个任务执行时间过长
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 0, TimeUnit.MILLISECONDS);

scheduExec.schedule(() -> {
System.out.println("执行第二个任务 " + format.format(new Date()));//不会影响第二个任务的执行时间
}, 0, TimeUnit.MILLISECONDS);

打印结果:

执行第一个任务  2018.06.12 11:34:49 406
执行第二个任务 2018.06.12 11:34:49 406

注意,上面使用 Timer 执行周期性任务时有说到:如果某个周期性任务的执行时间长度大于其周期时间长度,那么就会导致这一次的任务还在执行,而下一个周期的任务已经需要开始执行了,这样会导致越来越严重的时间延迟。

这个问题对于 ScheduledExecutorService 同样存在,JDK文档中是这么说的:对于 scheduleAtFixedRate ,如果此任务的任何一个执行要花费比其周期更长的时间,则将推迟后续执行,但不会同时执行。

2018-5-30

线程 Timer TimerTask 计时器 定时任务 MD的更多相关文章

  1. Timer&TimerTask原理分析

    转载地址,请珍惜作者的劳动成果,转载请注明出处:http://www.open-open.com/lib/view/open1337176725619.html 如果你使用Java语言进行开发,对于定 ...

  2. JDK Timer & TimerTask

    目录 Timer & TimerTask Binary Heap Insert DELETE MIN PERFORMANCE LifeCycle Constructor MainLoop sc ...

  3. 倒计时 总结 Timer Handler CountDownTimer RxJava MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  4. timer和ScheduledThreadPoolExecutor定时任务和每日固定时间执行

    //ScheduledThreadPoolExecutor每三秒执行一次 public static void main(String[] args) {        ScheduledThread ...

  5. 使用TimerTask创建定时任务

    使用TimerTask创建定时任务,打包之后应用于linux系统 step1:创建java项目 step2:代码实现 定时任务实现类CreateTask.java是打印操作者的名字 配置准换类Conf ...

  6. java中计时器的用法Timer和TimerTask的用法__java中利用Timer与TImerTask 计时器间隔执行任务

          经常我们都会有这样的需求,要固定的每隔一段时间执行某一个任务.比如:   我们做一个缓存来减少与数据库的交互,而为了使缓存与数据库中的数据尽量达到同步,需要每个固定的一段时间去数据库中的数 ...

  7. Timer,TimerTask通过程序计数器实现的定时任务

    1.程序计数器 程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看 做是当前线程所执行的字节码的行号指示器.在虚拟机的概念模型里(仅是概念模型, 各种虚 ...

  8. 服务器启动完成执行定时任务Timer,TimerTask

    由于项目需求:每隔一段时间就要调外部接口去进行某些操作,于是在网上找了一些资料,用了半天时间弄好了,代码: import java.util.TimerTask; public class Accou ...

  9. Android 中执行定时任务 Timer + TimerTask

    1. new Timer().schedule(new TimerTask() { @Override public void run() { //任务代码 } }, 0, 5000);

随机推荐

  1. Initializing the FallBack certificate failed . TDSSNIClient initialization failed

    安装SQL后服务不能启动,报错: 2014-03-24 14:33:10.06 spid13s     Error: 17190, Severity: 16, State: 1.2014-03-24 ...

  2. 2809: [Apio2012]dispatching 可并堆 左偏树

    https://www.lydsy.com/JudgeOnline/problem.php?id=2809 板子题wa了一下因为输出ans没有lld #include<iostream> ...

  3. OD基本汇编指令

    jmp ;无条件跳转 指哪飞哪 一些杂志中说的直飞光明顶,指的就是它了~ 光明顶一般指爆破地址根据条件跳转的指令:JE ;等于则跳转 JNE ;不等于则跳转 JZ ;为 0 则跳转   JNZ ;不为 ...

  4. bzoj2243 染色

    Description 给定一棵有n个节点的无根树和m个操作,操作有2类: 1.将节点a到节点b路径上所有点都染成颜色c: 2.询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段), 如 ...

  5. Nodejs线上日志部署

    Nodejs 被越来越多的使用到线上系统中,但线上系统没有日志怎么行呢. 一.forever记录日志 我的线上系统使用forever来启动服务,最开始就直接使用了forever来记录 forever ...

  6. ROS知识(18)----Pluginlib原理

    目录 Overview Example Providing a Plugin Registering/Exporting a Plugin The Plugin Description File Re ...

  7. 如何使用git工具向github提交代码

    大致分为以下几个步骤 安装git环境,工具使用msysgit github上的账号 首先在github上点击头像旁边的加号 add new ,选择new Repository,自己创建一个名字,假设取 ...

  8. 介绍一下开源项目FastAnimationWithPOP

    介绍一下开源项目FastAnimationWithPOP JUL 23RD, 2014 这是一个非常easy的动画框架,基于Facebook的POP库. 使用它你就能够在故事版中以0行代码的代价来加入 ...

  9. Clever Little Box 电缆组件 USB A 插座 至 USB B 插头

    http://china.rs-online.com/web/p/usb-cable-assemblies/7244156/ 产品详细信息 USB3.0适配器 superspeed USB将提供10x ...

  10. patch 用法

    diff -Nrua a b > c.patch 实例说明: --- old/modules/pcitable Mon Sep 27 11:03:56 1999 +++ new/modules/ ...