2019独角兽企业重金招聘Python工程师标准>>>

定时器

1 问题描述

1) 与每个 Timer 对象相对应的是单个后台线程,用于顺序地执行所有计时器任务。计时器任务应该迅速完成。如果完成某个计时器任务的时间太长,那么它会“独占”计时器的任务执行线程。因此,这就可能延迟后续任务的执行,而这些任务就可能“堆在一起”。    --摘自jdk1.6文档

2) 如果某个任务抛出了异常,整个Timer会停止,后继任务都不能被执行

下面笔者通过编程来重现以上两个问题。

/** * Timer执行周期任务,任务间隔5秒
*/
public class Test {public static void main(String[] args) {Timer timer = new Timer();//周期任务,任务间隔5stimer.schedule(task, 0, 5*1000);}static TimerTask task = new TimerTask() {@Overridepublic void run() {System.out.println(System.currentTimeMillis()/1000L);//注释1. 暂停10s,延长任务执行时间
//          try {
//              Thread.sleep(10 * 1000);
//          } catch (Exception e) {
//          }//         throw new RuntimeException("execption test");//注释2. 异常测试。}}
}

执行结果如下:

说明 结果 分析
直接执行

任务执行时间戳间隔为5秒,与预期相符
打开注释1,延长任务执行时间

任务执行间隔为10秒,与预期不符。印证了问题1).
关闭注释1,打开注释2.

只打印了一个时间就抛出了异常

执行任务抛出异常,程序退出,但不要因此而小看这个问题,如果把它放在servlet容器(如tomcat)或在某个框架中运行,虽然抛出了异常但程序很可能会继续执行的,如果异常没有记录到日志中,那你就头疼去吧。

2.问题分析

通过查看Timer源码我们可以发现Timer本质上是将所有要进行调度的TimerTask放进一个根据下次执行时间nextExecutionTime排序的队列中(TaskQueue实例,它最大能存储128个TimerTask),然后通过一个线程(TimerThread实例)来循环执行此队列中的任务。因此Timer调度的任务是在一个线程中执行的。而如果其中某一任务运行时间大于任务间执行间隔,会阻塞后续任务,所以Timer的定时在时间上是不严格的。而且对于任务执行时出现的异常也没有处理,所以它也是不可靠的。这两个问题也存在于ScheduledExecutorService的scheduleAtFixedRate方法中。

如下是TimerThread的主循环方法:

   /*** The main timer loop.  (See class comment.)*/private void mainLoop() {while (true) {try {TimerTask task;boolean taskFired;synchronized(queue) {// Wait for queue to become non-emptywhile (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 thinglong 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, removequeue.removeMin();task.state = TimerTask.EXECUTED;} else { // Repeating task, reschedulequeue.rescheduleMin(task.period<0 ? currentTime   - task.period: executionTime + task.period);}}}if (!taskFired) // Task hasn't yet fired; waitqueue.wait(executionTime - currentTime);}if (taskFired)  // Task fired; run it, holding no lockstask.run(); //此处执行被调度的任务,没有对运行时异常作处理} catch(InterruptedException e) {}}}

ThreadLocal

1.问题描述

你觉得下面这段代码有没有问题?

public class Test {public static void main(String[] args) {//取得一个10个线程的线程池ScheduledExecutorService executor = Executors.newScheduledThreadPool(5);for(int i=0; i<100; i++){//每隔200ms执行一次executor.schedule(new ProblematicCommand(), 200, TimeUnit.MILLISECONDS);}//加入调度的线程任务全部执行后关闭线程池,程序退出。executor.shutdown();}static class ProblematicCommand implements Runnable{static ThreadLocal<SomeResource> resources = new ThreadLocal<SomeResource>(){@Overrideprotected SomeResource initialValue() {System.out.printf("[Thread-%2d] init resource\r\n", Thread.currentThread().getId());return new SomeResource();}};public void releaseResource(){resources.get().release();}public void run() {resources.get().use();releaseResource(); //label 1: 释放资源}}static class SomeResource{private boolean released=false;//使用资源,如果资源已释放会抛出运行时异常public void use() {if(!released)System.out.printf("[Thread-%2d] Functional Tool is used\r\n", Thread.currentThread().getId());elsethrow new RuntimeException("resource has already been closed.");}//释放资源public void release(){this.released=true;System.out.printf("[Thread-%2d] resource released.\r\n", Thread.currentThread().getId());}}
}

我们将上面的代码运行起来后会发现它没有执行预期的次数(100次)就退出了,在我的电脑上运行结果为:初始化资源5次,使用5次,释放资源5次。

而如果把label 1代码注释掉,执行后发现代码只初始化了5次资源,但使用了100次!

2.问题分析

因为使用了线程池,所以上面的那些任务是在特定的几个线程中执行的(即5个线程共执行了100次任务),这就导致了ThreadLocal变量中只储存了与线程池中的线程ID相关的线程变量(ThreadLocal可以简单理解为Map<线程ID, SomeResource>)。也就是说某一个线程中执行了多次任务,因此在这个线程中执行的多个任务使用的却是同一个线程ID, 这就是资源只初始化5次的原因。而如果在任务执行完成后释放了资源,那么在释放资源的线程中再次执行任务时,由于与该线程ID绑定的资源已经被释放,在使用资源时抛出了异常,而线程池在发现异常后会导致整个线程池,所以程序直接退出了。

结论

有某任务需要循环执行,设其单次执行时间为t, 执行间隔为g,当t<g时定时器可以按时执行,但当t>=g时,任务被执行的间隔就成为了t.

另外在调度任务时要注意可能出现的异常。Timer在出现异常时会打印出异常栈,而ScheduledExecutorService却没有任何提示,但它们都会停止对任务的执行。

而在线程池中使用ThreadLocal,或通过定时器循环调度某一任务时(Timer可以看成是拥有一个线程的线程池)要注意ThreadLocal所持有的资源的状态。

转载于:https://my.oschina.net/wisedream/blog/313794

java定时器与ThreadLocal编程陷阱相关推荐

  1. Java开发人员在编程中常见的雷!

    身为一名Java从业人员,其职场生涯就是一边踩"坑",一边上升的过程.这个过程中不仅要学会修改无数bug,也要学会越过很多"坑".今天,千锋老师为大家分享一些J ...

  2. 分享一些Java开发人员在编程中最容易踩雷的地方!

    身为一名Java从业人员,其职场生涯就是一边踩"坑",一边上升的过程.这个过程中不仅要学会修改无数bug,也要学会越过很多"坑".今天,小千为大家分享一些Jav ...

  3. 一些Java开发人员在编程中常见的雷!

    身为一名Java从业人员,其职场生涯就是一边踩"坑",一边上升的过程.这个过程中不仅要学会修改无数bug,也要学会越过很多"坑".今天,小千为大家分享一些Jav ...

  4. Java增强之并发编程

    Java增强之并发编程 1 多线程 1.1 进程及线程 程序启动的时候,电脑会把这个程序加载到内存,在内存中需要给当前的程序分配一段的独立运行的空间,这个空间就专门负责这个程序的运行.每个应用程序运行 ...

  5. 4问教你搞定java中的ThreadLocal

    摘要:ThreadLocal是除了加锁同步方式之外的一种保证规避多线程访问出现线程不安全的方法. 本文分享自华为云社区<4问搞定java中的ThreadLocal>,作者:breakDra ...

  6. 《Java线程与并发编程实践》—— 1.2 操作更高级的线程任务

    本节书摘来异步社区<Java线程与并发编程实践>一书中的第1章,第1.2节,作者: [美]Jeff Friesen,更多章节内容可以访问云栖社区"异步社区"公众号查看. ...

  7. Java多线程与并发编程终极宝典

    阅读本文需要了解的概念 原语 所谓原语,一般是指由若干条指令组成的程序段,用来实现某个特定功能,在执行过程中不可被中断.在操作系统中,某些被进程调用的操作,如队列操作.对信号量的操作.检查启动外设操作 ...

  8. java 延时发送邮件_java编程实现邮件定时发送的方法

    本文实例讲述了java编程实现邮件定时发送的方法.分享给大家供大家参考,具体如下: 最近做项目时客户提出了一个需求:系统定时发送E-mail到其客户,达到通知的效果.先将实例分享给大家,如果确实有一些 ...

  9. 阿里Java开发手册之编程规约

    阿里Java开发手册之编程规约 对于程序员来说,编程规范可以养成良好的编程习惯,提高代码质量,降低沟通成本.就在2月9号,阿里出了一份Java开发手册(正式版),分为编程规约,异常日志,MySQL规约 ...

最新文章

  1. bool类型未初始化的产生的奇怪现象
  2. Spark streaming java代码
  3. sql CHECK ,UNIQUE 约束(mysql)
  4. python字符串是什么_python字符串详解
  5. 分区表理论解析(下):SQL Server 2k52k8系列(二)
  6. python基础到实践教程_Python从入门到实践案例教程(21世纪高等学校计算机教育实用规划教材)...
  7. 发送邮件(注册用户并激活邮箱)
  8. Webpack——样式处理
  9. 软件测试 — 面试题
  10. AUTOCAD——图块批量改名
  11. windows批处理备份压缩文件夹rar
  12. 二代测序(Next generation sequencing)介绍
  13. 程序员的幽默笑话(深意爆笑)
  14. 损失函数,mse,cee
  15. 手机拍照技巧:全景拍摄,让手机拍出的照片妙趣横生
  16. java中除法和取余的若干注意
  17. 学python怎么样
  18. 操作系统——操作系统发展历程及基本概念
  19. 【量化投资】策略一(聚宽)
  20. 《JAVA 中的 -> 是什么意思?》

热门文章

  1. c语言入门经典课后作业,C语言入门经典习题答案.doc
  2. linux scp 隐藏文件,scp 客户端发现了隐藏 35 年的漏洞
  3. LKT系列加密芯片如何预置openssl生成的rsa密钥完成运算
  4. 浅谈过程和结果的关系
  5. 图像超分辨率进ASC19超算大赛,PyTorch+GAN受关注
  6. Adobe放出P图新研究:就算丢了半个头,也能逼真复原
  7. 在哈佛的一场闭门会上,专家说全球各国都应设置“人工智能部长”
  8. “现有人工智能都是二流的”
  9. 为什么要使用Redis?
  10. httpd 服务的两个节点的HA