Timer&TimerTask原理分析
转载地址,请珍惜作者的劳动成果,转载请注明出处:http://www.open-open.com/lib/view/open1337176725619.html
如果你使用Java语言进行开发,对于定时执行任务这样的需求,自然而然会想到使用Timer和TimerTask完成任务,我最近就使用 Timer和TimerTask完成了一个定时执行的任务,实现得没有问题,但当在TimerTaks的run()方法中使用 Thread.sleep()方式时,可能会出现奇怪的现象,好像Timer失效了,网上查了一下,倒是有人遇到了相同的问题,但是并没有找到一篇解释为什么会出现这种情况,期待有某位达人能够分析清楚这个问题。
遇到了这样的问题,始终让我不爽,于是看了一下Timer的源码,先将了解到的内容整理如下,接下来再看看Thread.sleep()的源码,看能否找到问题所在。
在Java中,与定时任务执行相关的类很少,只有Timer、TimerTask、TimerThread、TaskQueue几个,其中每个类的职责大致如下:
Timer:一个Task的调度类,和TimerTask一样,暴露给最终用户使用的类,通过schedule方法安排Task的执行计划。该类通过TaskQueue和TimerThread类完成Task的调度。
TimerTask:实现Runnable接口,表明每一个任务均为一个独立的线程。通过run()方法提供用户定制自己任务。该类有一个比较重要的成员变量nextExecutionTime ,表示下一次执行该任务的时间。以后会看到,Timer机制就是靠这个值安排Task执行的。
TimerThread:继承于Thread,是真正执行Task的类。
TaskQueue:一个存储Task的数据结构,内部由一个最小堆实现,堆的每个成员为一个TimeTask,每个Task依靠其 nextExecutionTime值进行排序,也就是说,nextExecutionTime最小的任务在队列的最前端,从而能够现实最早执行。
要想使用Timer,用户只需要了解Timer和TimerTask,下面现已一个最基本的Timer和TimerTask使用案例入手,来看一下Timer内部的实现原理。
import java.util.Timer; import java.util.TimerTask; import org.junit.Test; class TestTimerTask extends TimerTask { @Override public void run() { System.out.println("TestTimerTask is running......"); } } public class TimerTaskTest { @Test public void testTimerTask() { Timer timer = new Timer(); timer.schedule(new TestTimerTask(), 0, 10); } }
上面的代码是一个典型的Timer&TimerTask的应用,下面先来看一下new Timer()干了什么事,其源码如下:
public Timer(String name) {
thread.setName(name); //thread为TimerThread实例。
thread.start();
}
从上面的源代码可以知道,创建Timer对象的同时也启动了TimerThread线程。下面来看看TimerThread干了什么事:
public void run() { try { mainLoop(); //线程真正执行的代码在这个私有方法中 } finally { // Someone killed this Thread, behave as if Timer cancelled synchronized(queue) { newTasksMayBeScheduled = false; queue.clear(); // Eliminate obsolete references } } }
接着来看看私有方法mainLoop()干了什么事:
private void mainLoop() { while (true) { try { TimerTask task; boolean taskFired; //是否已经到达Task的执行时间,如果已经到达,设置为true,否则置为false synchronized(queue) { // Wait for queue to become non-empty while (queue.isEmpty() && newTasksMayBeScheduled) queue.wait(); //由此可以看出,Timer通过wait & notify 方法安排线程之间的同步 if (queue.isEmpty()) break; // Queue is empty and will forever remain; die // Queue nonempty; look at first evt and do the right thing long 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)) { //Task的执行时间已到,设置taskFired为true if (task.period == 0) { // Non-repeating, remove queue.removeMin(); //移除队列中的当前任务 task.state = TimerTask.EXECUTED; } else { // Repeating task, reschedule queue.rescheduleMin( //重新设置任务的下一次执行时间 task.period<0 ? currentTime - task.period : executionTime + task.period); } } } if (!taskFired) // Task hasn't yet fired; wait queue.wait(executionTime - currentTime); //还没有执行时间,通过wait等待特定时间 } if (taskFired) // Task fired; run it, holding no locks task.run(); //已经到达执行时间,执行任务 } catch(InterruptedException e) { } } }
也就是说,一旦创建了Timer类的实例,就一直存在一个循环在遍历queue中的任务,如果有任务的话,就通过thread去执行该任务,否则线程通过wait()方法阻塞自己,由于没有任务在队列中,就没有必要继续thread中的循环。
上面提到,如果Timer的任务队列中不包含任务时,Timer中的TimerThread线程并不会执行,接着来看看为Timer添加任务后会出现怎样的情况。为Timer添加任务就是timer.schedule()干的事,schedule()方法直接调用Timer的私有方法 sched(),sched()是真正安排Task的地方,其源代码如下:
private void sched(TimerTask task, long time, long period) { if (time < 0) throw new IllegalArgumentException("Illegal execution time."); synchronized(queue) { if (!thread.newTasksMayBeScheduled) throw new IllegalStateException("Timer already cancelled."); synchronized(task.lock) { if (task.state != TimerTask.VIRGIN) //我喜欢virgin状态,其他状态表明该Task已经被schedule过了 throw new IllegalStateException( "Task already scheduled or cancelled"); //设置Task下一次应该执行的时间, 由System.currentTimeMillis()+/-delay得到 task.nextExecutionTime = time; task.period = period; task.state = TimerTask.SCHEDULED; } queue.add(task); //queue为TaskQueue类的实例,添加任务到队列中 if (queue.getMin() == task) //获取队列中nextExecutionTime最小的任务,如果与当前任务相同 queue.notify(); //还记得前面看到的queue.wait()方法么 } }
不要奇怪,为什么要判断queue.getMin() == task时,才通过queue.notify()恢复执行。因为这种方式已经满足所有的唤醒要求了。
如果安排当前Task之前queue为空,显然上述判断为true,于是mainLoop()方法能够继续执行。
如果安排当前Task之前queue不为空,那么mainLoop()方法不会一直被阻塞,不需要notify方法调用。
调用该方法还有一个好处是,如果当前安排的Task的下一次执行时间比queue中其余Task的下一次执行时间都要小,通过notify方法可以提前打开queue.wait(executionTime - currentTime)方法对mainLoop()照成的阻塞,从而使得当前任务能够被优先执行,有点抢占的味道。
上述分析可以看出,Java中Timer机制的实现仅仅使用了JDK中的方法,通过wait & notify机制实现,其源代码也非常简单,但可以想到的是这种实现机制会对开发者造成一种困扰,sched()方法中可以看出,对于一个重复执行的任务,Timer的实现机制是先安排Task下一次执行的时间,然后再启动Task的执行,如果Task的执行时间大于下一次执行的间隔时间,可能出现不可预期的错误。当然,了解了Timer的实现原理,修改这种实现方式也就非常简单了。
Timer&TimerTask原理分析的更多相关文章
- Java并发编程笔记之Timer源码分析
timer在JDK里面,是很早的一个API了.具有延时的,并具有周期性的任务,在newScheduledThreadPool出来之前我们一般会用Timer和TimerTask来做,但是Timer存在一 ...
- Java Timer定时器原理
做项目很多时候会用到定时任务,比如在深夜,流量较小的时候,做一些统计工作.早上定时发送邮件,更新数据库等.这里可以用Java的Timer或线程池实现.Timer可以实现,不过Timer存在一些问题.他 ...
- RocketMQ延迟消息的代码实战及原理分析
RocketMQ简介 RocketMQ是一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的.高可靠.万亿级容量.灵活可伸缩的消息发布与订阅服务. 它前身是MetaQ,是阿里基于Kafka ...
- web压测工具http_load原理分析
一.前言 http_load是一款测试web服务器性能的开源工具,从下面的网址可以下载到最新版本的http_load: http://www.acme.com/software/http_load/ ...
- 消息队列NetMQ 原理分析2-IO线程和完成端口
消息队列NetMQ 原理分析2-IO线程和完成端口 前言 介绍 目的 IO线程 初始化IO线程 Proactor 启动Procator线程轮询 处理socket 获取超时时间 从完成端口获取处理完的状 ...
- go-common-pool设计原理分析
common-pool: 对于一些对象的频繁创建会带来很大的系统开销,并且需要对对象数量进行控制来降低资源消耗,比如数据库连接,线程等 common-pool采用了缓存思想来解决这个问题,预先把一些对 ...
- java timer timertask mark
其实就Timer来讲就是一个调度器,而TimerTask呢只是一个实现了run方法的一个类,而具体的TimerTask需要由你自己来实现,例如这样: 1 2 3 4 5 6 Timer timer = ...
- DexHunter的原理分析和使用说明(二)
本文博客地址:http://blog.csdn.net/qq1084283172/article/details/53715325 前面的博文<Android通用脱壳工具DexHunter的原理 ...
- Handler系列之原理分析
上一节我们讲解了Handler的基本使用方法,也是平时大家用到的最多的使用方式.那么本节让我们来学习一下Handler的工作原理吧!!! 我们知道Android中我们只能在ui线程(主线程)更新ui信 ...
随机推荐
- 【poj2724】 Purifying Machine
http://poj.org/problem?id=2724 (题目链接) 题意 Mike有一个机器可以帮助他清理奶酪,每个奶酪由一个n位二进制数表示,机器上一共有n个按钮,每个按钮有1,0,*,其中 ...
- 初识A*算法
写这篇文章的初衷是应一个网友的要求,当然我也发现现在有关人工智能的中文站点实在太少,我在这里抛砖引玉,希望大家都来热心的参与. 还是说正题,我先拿A*算法开刀,是因为A*在游戏中有它很典型的用法,是人 ...
- eclipse中新建javaweb项目,查看某些类的源码
网上查到的大多说在项目下选择properties,在java Build Path下Add ExternalJAR Selection,加上Tomcat->lib下的servlet-api.ja ...
- AppSettingManager
public class AppSettingManager { public static bool Update(string key, string value) { try { var con ...
- @suppressWarnings解释
J2SE 提供的最后一个批注是 @SuppressWarnings.该批注的作用是给编译器一条指令,告诉它对被批注的代码元素内部的某些警告保持静默. 一点背景:J2SE 5.0 为 Java 语言增加 ...
- QQ 腾讯QQ(简称“QQ”)是腾讯公司开发的一款基于Internet的即时通信(IM)软件
QQ 编辑 腾讯QQ(简称“QQ”)是腾讯公司开发的一款基于Internet的即时通信(IM)软件.腾讯QQ支持在线聊天.视频通话.点对点断点续传文件.共享文件.网络硬盘.自定义面板.QQ邮箱等多种功 ...
- 八大常见内排序java实现
虽然排序算法烂大街了,但是哥依然用java实现了一遍,只为自己练练手,后面可以时不时的回头看看...仅此而已,各位可以提意见,莫喷!! 一.冒泡排序 基本思想:在要排序的一组数中,对当前还未排好序的范 ...
- PowerDesigner15在win7-64位系统下对MySQL反向工程
由于机器是win64位的,下载的64的connector安装测试成功,但是在powerdesigner中测试连不上,总算在下面这边博友中找到解决方案! http://blog.csdn.net/web ...
- 总结使用Unity 3D优化游戏运行性能的经验
原地址:http://www.gameres.com/msg_221889.html 作者:Amir Fasshihi 流畅的游戏玩法来自流畅的帧率,而我们即将推出的动作平台游戏<Shadow ...
- ruby : Exception Notification
https://github.com/smartinez87/exception_notification#sections Add the following line to your applic ...