如果你使用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内部的实现原理。
01 |
import java.util.Timer; |
03 |
import java.util.TimerTask; |
05 |
import org.junit.Test; |
09 |
class TestTimerTask extends TimerTask
{ |
15 |
System.out.println("TestTimerTask
is running......"); |
21 |
public class TimerTaskTest
{ |
25 |
public void testTimerTask()
{ |
27 |
Timer
timer = new Timer(); |
29 |
timer.schedule(new TestTimerTask(), 0, 10); |
上面的代码是一个典型的Timer&TimerTask的应用,下面先来看一下new Timer()干了什么事,其源码如下:
public Timer(String name) {
thread.setName(name); //thread为TimerThread实例。
thread.start();
}
从上面的源代码可以知道,创建Timer对象的同时也启动了TimerThread线程。下面来看看TimerThread干了什么事:
05 |
mainLoop(); //线程真正执行的代码在这个私有方法中 |
09 |
//
Someone killed this Thread, behave as if Timer cancelled |
13 |
newTasksMayBeScheduled
= false; |
15 |
queue.clear(); //
Eliminate obsolete references |
接着来看看私有方法mainLoop()干了什么事:
01 |
private void mainLoop()
{ |
09 |
booleantaskFired; //是否已经到达Task的执行时间,如果已经到达,设置为true,否则置为false |
13 |
//
Wait for queue to become non-empty |
15 |
while (queue.isEmpty()
&& newTasksMayBeScheduled) |
17 |
queue.wait(); //由此可以看出,Timer通过wait
& notify 方法安排线程之间的同步 |
21 |
break; //
Queue is empty and will forever remain; die |
25 |
//
Queue nonempty; look at first evt and do the right thing |
27 |
long currentTime,
executionTime; |
29 |
task
= queue.getMin(); |
31 |
synchronized(task.lock)
{ |
33 |
if (task.state
== TimerTask.CANCELLED) { |
37 |
continue; //
No action required, poll queue again |
41 |
currentTime
= System.currentTimeMillis(); |
43 |
executionTime
= task.nextExecutionTime; |
45 |
if(taskFired
= (executionTime<=currentTime)) { //Task的执行时间已到,设置taskFired为true |
47 |
if (task.period
== 0)
{ //
Non-repeating, remove |
49 |
queue.removeMin(); //移除队列中的当前任务 |
51 |
task.state
= TimerTask.EXECUTED; |
53 |
} else { //
Repeating task, reschedule |
55 |
queue.rescheduleMin( //重新设置任务的下一次执行时间 |
57 |
task.period<0 ?
currentTime - task.period |
59 |
:
executionTime + task.period); |
如果服务在当天00:01分以前被启动,两个方法都会在在00:01分时执行任务. 第二次任务执行时间都是00:01+24
* 60 * 60 * 1000,如果过了这个点后启服务两个方法都会马上执行任务.但第二次任务被执行的时间就是差别了,scheduleAtFixedRate是在你设置的date的基础上加24 * 60 * 60 * 1000这个时间段后执行而,schedule是在服务启动时间的基础上加24 * 60 * 60 * 1000这个时间段后执行.
67 |
if (!taskFired) //
Task hasn't yet fired; wait |
69 |
queue.wait(executionTime
- currentTime); //还没有执行时间,通过wait等待特定时间 |
73 |
if (taskFired) //
Task fired; run it, holding no locks |
75 |
task.run(); //已经到达执行时间,执行任务 |
77 |
} catch(InterruptedException
e) { |
也就是说,一旦创建了Timer类的实例,就一直存在一个循环在遍历queue中的任务,如果有任务的话,就通过thread去执行该任务,否则线程通过wait()方法阻塞自己,由于没有任务在队列中,就没有必要继续thread中的循环。
上面提到,如果Timer的任务队列中不包含任务时,Timer中的TimerThread线程并不会执行,接着来看看为Timer添加任务后会出现怎样的情况。为Timer添加任务就是timer.schedule()干的事,schedule()方法直接调用Timer的私有方法 sched(),sched()是真正安排Task的地方,其源代码如下:
01 |
private void sched(TimerTask
task, long time, long period)
{ |
05 |
throw new IllegalArgumentException("Illegal
execution time."); |
11 |
if (!thread.newTasksMayBeScheduled) |
13 |
throw new IllegalStateException("Timer
already cancelled."); |
17 |
synchronized(task.lock)
{ |
19 |
if(task.state
!= TimerTask.VIRGIN) //我喜欢virgin状态,其他状态表明该Task已经被schedule过了 |
21 |
throw new IllegalStateException( |
23 |
"Task
already scheduled or cancelled"); |
27 |
//设置Task下一次应该执行的时间,
由System.currentTimeMillis()+/-delay得到 |
29 |
task.nextExecutionTime
= time; |
33 |
task.state
= TimerTask.SCHEDULED; |
39 |
queue.add(task); //queue为TaskQueue类的实例,添加任务到队列中 |
41 |
if(queue.getMin()
== task) //获取队列中nextExecutionTime最小的任务,如果与当前任务相同 |
43 |
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原理分析
转载地址,请珍惜作者的劳动成果,转载请注明出处:http://www.open-open.com/lib/view/open1337176725619.html 如果你使用Java语言进行开发,对于定 ...
- Java Reference核心原理分析
本文转载自Java Reference核心原理分析 导语 带着问题,看源码针对性会更强一点.印象会更深刻.并且效果也会更好.所以我先卖个关子,提两个问题(没准下次跳槽时就被问到). 我们可以用Byte ...
- Java Timer定时器原理
做项目很多时候会用到定时任务,比如在深夜,流量较小的时候,做一些统计工作.早上定时发送邮件,更新数据库等.这里可以用Java的Timer或线程池实现.Timer可以实现,不过Timer存在一些问题.他 ...
- java timer timertask mark
其实就Timer来讲就是一个调度器,而TimerTask呢只是一个实现了run方法的一个类,而具体的TimerTask需要由你自己来实现,例如这样: 1 2 3 4 5 6 Timer timer = ...
- Java 线程池原理分析
1.简介 线程池可以简单看做是一组线程的集合,通过使用线程池,我们可以方便的复用线程,避免了频繁创建和销毁线程所带来的开销.在应用上,线程池可应用在后端相关服务中.比如 Web 服务器,数据库服务器等 ...
- Java Timer, TimerTask, Timer.Schedule
schedule的意思(时间表.进度表) timer.schedule(new TimerTask(){ void run()},0, 60*60*1000);timer.schedule(new M ...
- java string 细节原理分析(2016.5)
看到了以前2016.5月学习java写的笔记,这里放在一起. String实现的细节原理分析 一.jdk源码中String 的实现 public final class String implemen ...
- Java Timer TimerTask Example(java Timer的例子)
Java java.util.Timer is a utility class that can be used to schedule a thread to be executed at cert ...
- Java程序运行原理分析
class文件内容 class文件包含Java程序执行的字节码 数据严格按照格式紧凑排列在class文件的二进制流,中间无分割符 文件开头有一个0xcafebabe(16进制)特殊的标志 JVM运行时 ...
- Java 中 ConcurrentHashMap 原理分析
一.Java并发基础 当一个对象或变量可以被多个线程共享的时候,就有可能使得程序的逻辑出现问题. 在一个对象中有一个变量i=0,有两个线程A,B都想对i加1,这个时候便有问题显现出来,关键就是对i加1 ...
随机推荐
- Echarts + 低代码 :可视化如何赋能企业的创新之路?
前言 数据驱动已经成为企业决策和业务优化的关键所在,在数字化时代,高效的数据分析与可视化呈现是实现智能决策的关键.利用低代码开发平台,企业可以快速构建满足业务需求的应用系统,实现对各类数据源的便捷接入 ...
- .NET 8 微软免费开源的 Blazor UI 组件库
前言 .NET 8 的发布,微软推出了官方免费且开源的 Blazor UI 组件库 -- Fluent UI Blazor. 组件库提供了Web应用程序所需的工具,确保应用程序能够与 Microsof ...
- 音视频FAQ(二)视频直播延时高
摘要 延时高是实时互动技术中常见的问题之一,解决延时高问题需要综合考虑网络.设备.编解码算法等多个因素.解决方案包括优化设备端延时.优化网络传输延时和使用UDP进行音视频传输等.在选择音视频传输协议时 ...
- 爬虫案例2-爬取视频的三种方式之一:selenium篇(2)
@ 目录 前言 selenium简介 实战案例 共勉 博客 前言 继使用requests库爬取好看视频的文章后,本文分享使用python第三方库selenium库接着来爬取视频网站,后续也会接着分享使 ...
- .NET 9 RC1 正式发布
.NET 9 RC1 是 .NET 9 的第一个候选发布版本(Release Candidate),标志着该版本接近最终发布.根据Github 上相关的内容,我们可以总结出以下几点: 新功能和改进: ...
- 使用Hexo主题搭建个人博客(markdown)
依赖环境 安装node.js:node.js下载可以从其官方界面开始https://nodejs.org/zh-cn/ 安装git:git下载则可以从其官方界面开始https://git-scm.co ...
- CSS & JS Effect – 用 wheel 模拟 scroll
前言 在 用 JavaScript 实现 position sticky 文章中,我提到了用 wheel 来模拟 scroll 效果. 这篇来说说具体怎么实现,挺简单的哦. Preparation t ...
- C++ STL set/multiset容器
set/multiset容器 简介 Set的特性是,所有元素都会根据元素的值自动被排序.Set不允许两个元素有相同的值. Set的迭代器iterator是一种const_iterator,不能通过迭代 ...
- Linux 防火墙与安全管理工具详解
Linux 防火墙与安全管理工具详解 1. Iptables 概述 Iptables 是 Linux 系统中用于控制网络流量的工具,通过定义规则来过滤.转发和修改数据包.其规则可以细致地管理进入和离开 ...
- TS体操类型学习记录
Easy 1. Easy - 4 - Pick 从类型 T 中选出符合 K 的属性,构造一个新的类型 type MyPick<T, K extends keyof T> = { [key ...