原文作者:妮蔻

原文地址:Java并发编程笔记之Timer源码分析

目录

一、timer问题复现

二、Timer 实现原理分析


timer在JDK里面,是很早的一个API了。具有延时的,并具有周期性的任务,在newScheduledThreadPool出来之前我们一般会用Timer和TimerTask来做,但是Timer存在一些缺陷,为什么这么说呢?

  • Timer只创建唯一的线程来执行所有Timer任务。如果一个timer任务的执行很耗时,会导致其他TimerTask的时效准确性出问题。例如一个TimerTask每10秒执行一次,而另外一个TimerTask每40ms执行一次,重复出现的任务会在后来的任务完成后快速连续的被调用4次,要么完全“丢失”4次调用。
  • Timer的另外一个问题在于,如果TimerTask抛出未检查的异常会终止timer线程。这种情况下,Timer也不会重新回复线程的执行了;它错误的认为整个Timer都被取消了。此时已经被安排但尚未执行的TimerTask永远不会再执行了,新的任务也不能被调度了。

一、timer问题复现

这里做了一个小的 demo 来复现问题,代码如下:

package com.hjc;import java.util.Timer;
import java.util.TimerTask;public class TimerTest {//创建定时器对象static Timer timer = new Timer();public static void main(String[] args) {//添加任务1,延迟500ms执行timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("---one Task---");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}throw new RuntimeException("error ");}}, 500);//添加任务2,延迟1000ms执行timer.schedule(new TimerTask() {@Overridepublic void run() {for (;;) {System.out.println("---two Task---");try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}, 1000);}
}

如上代码先添加了一个任务在 500ms 后执行,然后添加了第二个任务在 1s 后执行,我们期望的是当第一个任务输出 ---one Task--- 后等待 1s 后第二个任务会输出 ---two Task---,

但是执行完毕代码后输出结果如下所示:

例子2:

public class Shedule {private static long start;public static void main(String[] args) {TimerTask task = new TimerTask() {public void run() {System.out.println(System.currentTimeMillis()-start);try{Thread.sleep(3000);}catch (InterruptedException e){e.printStackTrace();}}};TimerTask task1 = new TimerTask() {@Overridepublic void run() {System.out.println(System.currentTimeMillis()-start);}};Timer timer = new Timer();start = System.currentTimeMillis();//启动一个调度任务,1S钟后执行timer.schedule(task,1000);//启动一个调度任务,3S钟后执行timer.schedule(task1,3000);}}

上面程序我们预想是第一个任务执行后,第二个任务3S后执行的,即输出一个1000,一个3000.。实际运行结果如下:

实际运行结果并不如我们所愿。实际结果,是过了4S后才输出第二个任务,即4001约等于4秒。那部分时间时间到哪里去了呢?那个时间是被我们第一个任务的sleep所占用了。

现在我们在第一个任务中去掉Thread.sleep();这一行代码,运行是否正确了呢?运行结果如下:

可以看到确实是第一个任务过了1S后执行,第二个任务在第一个任务执行完后过3S执行了。这就说明了Timer只创建唯一的线程来执行所有Timer任务。如果一个timer任务的执行很耗时,会导致其他TimerTask的时效准确性出问题

二、Timer 实现原理分析

下面简单介绍下 Timer 的原理,如下图是 Timer 的原理模型介绍:

  1. 其中 TaskQueue 是一个平衡二叉树堆实现的优先级队列,每个 Timer 对象内部有唯一一个 TaskQueue 队列。用户线程调用 timer 的 schedule 方法就是把 TimerTask 任务添加到 TaskQueue 队列,在调用 schedule 的方法时候 long delay 参数用来说明该任务延迟多少时间执行。
  2. TimerThread 是具体执行任务的线程,它从 TaskQueue 队列里面获取优先级最小的任务进行执行,需要注意的是只有执行完了当前的任务才会从队列里面获取下一个任务而不管队列里面是否有已经到了设置的 delay 时间,一个 Timer 只有一个 TimerThread 线程,所以可知 Timer 的内部实现是一个多生产者单消费者模型。

从实现模型可以知道要探究上面的问题只需看 TimerThread 的实现就可以了,TimerThread 的 run 方法主要逻辑源码如下:

public void run() {try {mainLoop();} finally {// 有人杀死了这个线程,表现得好像Timer已取消synchronized(queue) {newTasksMayBeScheduled = false;queue.clear();  // 消除过时的引用}}
}private void mainLoop() {while (true) {try {TimerTask task;boolean taskFired;//从队列里面获取任务时候要加锁synchronized(queue) {......}if (taskFired)  task.run();//执行任务} catch(InterruptedException e) {}}}

可知当任务执行过程中抛出了除 InterruptedException 之外的异常后,唯一的消费线程就会因为抛出异常而终止,那么队列里面的其他待执行的任务就会被清除。所以 TimerTask 的 run 方法内最好使用 try-catch 结构 catch 主可能的异常,不要把异常抛出到 run 方法外。

其实要实现类似 Timer 的功能使用 ScheduledThreadPoolExecutor 的 schedule 是比较好的选择。ScheduledThreadPoolExecutor 中的一个任务抛出了异常,其他任务不受影响的。

ScheduledThreadPoolExecutor 例子如下:

public class ScheduledThreadPoolExecutorTest {static ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(1);public static void main(String[] args) {scheduledThreadPoolExecutor.schedule(new Runnable() {public void run()  {System.out.println("---one Task---");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}throw new RuntimeException("error ");}}, 500, TimeUnit.MICROSECONDS);scheduledThreadPoolExecutor.schedule(new Runnable() {public void run() {for (int i =0;i<5;++i) {System.out.println("---two Task---");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}, 1000, TimeUnit.MICROSECONDS);scheduledThreadPoolExecutor.shutdown();}
}

运行结果如下:

之所以 ScheduledThreadPoolExecutor 的其他任务不受抛出异常的任务的影响是因为 ScheduledThreadPoolExecutor 中的 ScheduledFutureTask 任务中 catch 掉了异常,但是在线程池任务的 run 方法内使用 catch 捕获异常并打印日志是最佳实践。

Java并发编程—定时器Timer底层原理相关推荐

  1. java虚拟机线程调优与底层原理分析_Java并发编程——多线程的底层原理

    " Java代码在编译后会变成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节码,最终需要转化为汇编指令在CPU上执行,Java中所使用的并发机制依赖于JVM的实现和 CPU的 ...

  2. Java并发编程 Synchronized及其实现原理

    Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法.Synchronized的作用主要有三个:(1)确保线程互斥的访问同步代码(2)保证共享变量的修改能够及时可见 ...

  3. Java并发编程-线程池底层工作原理

    线程池底层工作原理 1.线程池的底层工作流程 1.1.线程池的底层工作原理图 1.2.银行办理业务案例 1.3.线程池的底层工作流程总结 2.线程池用哪个?生产中如何设置合理参数 2.1.在工作中单一 ...

  4. Java并发编程:Synchronized底层优化(偏向锁、轻量级锁)

    一.重量级锁 上篇文章中向大家介绍了Synchronized的用法及其实现的原理.现在我们应该知道,Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的.但是监视器锁本质 ...

  5. Java并发编程—自旋锁CLHLock原理

    原文作者:知识铺 原文地址:Java知识进阶-程序员必懂的自旋锁CLHLock原理 目录 一.CLHLock 1.原理 2.获取锁步骤: 二.MCSLock 1.原理 2.步骤 一.CLHLock 1 ...

  6. Java并发编程-wait和notify原理剖析

    目录 1.小故事 - 为什么需要 wait 2.wait & notify工作原理 3.API 介绍 4.wait和notify的正确姿势 4.1.sleep(long n) 和 wait(l ...

  7. 学习笔记(11):Python网络编程并发编程-粘包底层原理分析

    立即学习:https://edu.csdn.net/course/play/24458/296241?utm_source=blogtoedu  1.send和recv底层分析 1)不管是recv还是 ...

  8. JAVA并发编程--2.synchronied实现原理

    synchronied实现原理 虚拟机锁原理 虚拟机中对象头部信息 /*hotspot/src/share/vm/oops/oop.hpp*/ class oopDesc {friend class ...

  9. Java并发编程—Synchronized底层优化(偏向锁、轻量级锁)

    原文作者:Matrix海 子 原文地址:Java并发编程:Synchronized底层优化(偏向锁.轻量级锁) 目录 一.重量级锁 二.轻量级锁 三.偏向锁 四.其他优化 五.总结 一.重量级锁 上篇 ...

最新文章

  1. 如何优雅的在python中暂停死循环?
  2. python nums函数_Python函数
  3. python 获取主机名 根据端口杀死进程
  4. vs 2005 與vs 2003 語法比較
  5. 【转载】[BetterExplained]为什么你应该(从现在开始就)写博客
  6. jupyter 显示全部数据
  7. swift -- 数组
  8. CodeForces - 1370F2 The Hidden Pair (Hard Version)(交互题+二分)
  9. nginx解析php失败,为什么nginx不能解析php?
  10. jpa mysql存储过程_spring data jpa 如何调用mysql存储过程?
  11. 2019下半年系统集成项目管理工程师下午真题
  12. html工作界面抽象吗,WEB界面设计五种特征-网页设计,HTML/CSS
  13. mysql v$session_关于V$SESSION视图
  14. centos eclipse php,centos打不开eclipse怎么办?
  15. 设计FMEA步骤五:风险分析
  16. 为了找出羞羞视频,百度云居然有“娇喘模式”
  17. 加密与解密(一) -- 壳、加壳
  18. verilog 定点数、浮点数是什么?怎么表示?怎么做运算?
  19. mysql outer join的用法_MySQL 8 中的连接语法JOIN、OUTER JOIN的相关用法
  20. 巴西龟饲养日志----肺炎治疗情况

热门文章

  1. Http怎么处理长连接
  2. Discuz! X2.5 添加自定义数据调用模块
  3. Jquery 三种方法获取取Checkbox是否选中
  4. The Definitive Guide to SWT and JFace 目录
  5. 多个project[项目]共享session
  6. Unity3D 中的程序后台运行
  7. 21.Odoo产品分析 (三) – 人力资源板块(2) – 工时表(1)
  8. 实验五 操作系统之存储管理
  9. javase_03作业
  10. 2011年 11月底-12月初 51Aspx源码发布详情