如果你使用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;
02  
03 import java.util.TimerTask;
04  
05 import org.junit.Test;
06  
07   
08  
09 class TestTimerTask extends TimerTask
{
10  
11   @Override
12  
13   public void run()
{
14  
15     System.out.println("TestTimerTask
is running......"
);
16  
17   }
18  
19 }
20  
21 public class TimerTaskTest
{
22  
23   @Test
24  
25   public void testTimerTask()
{
26  
27     Timer
timer = 
new Timer();
28  
29     timer.schedule(new TestTimerTask(), 010);
30  
31   }
32  
33 }

上面的代码是一个典型的Timer&TimerTask的应用,下面先来看一下new Timer()干了什么事,其源码如下:

public Timer(String name) {

thread.setName(name);    //thread为TimerThread实例。

thread.start();

}

从上面的源代码可以知道,创建Timer对象的同时也启动了TimerThread线程。下面来看看TimerThread干了什么事:

01 public void run()
{
02  
03         try {
04  
05             mainLoop();                 //线程真正执行的代码在这个私有方法中
06  
07         finally {
08  
09             //
Someone killed this Thread, behave as if Timer cancelled
10  
11             synchronized(queue)
{
12  
13                 newTasksMayBeScheduled
false;
14  
15                 queue.clear();  //
Eliminate obsolete references
16  
17             }
18  
19         }
20  
21 }

接着来看看私有方法mainLoop()干了什么事:

01 private void mainLoop()
{
02  
03         while (true)
{
04  
05             try {
06  
07                 TimerTask
task;
08  
09                 booleantaskFired;       //是否已经到达Task的执行时间,如果已经到达,设置为true,否则置为false
10  
11                 synchronized(queue)
{
12  
13                     //
Wait for queue to become non-empty
14  
15                     while (queue.isEmpty()
&& newTasksMayBeScheduled)
16  
17                         queue.wait();                //由此可以看出,Timer通过wait
& notify 方法安排线程之间的同步
18  
19                     if (queue.isEmpty())
20  
21                         break//
Queue is empty and will forever remain; die
22  
23   
24  
25                     //
Queue nonempty; look at first evt and do the right thing
26  
27                     long currentTime,
executionTime;
28  
29                     task
= queue.getMin();
30  
31                     synchronized(task.lock)
{
32  
33                         if (task.state
== TimerTask.CANCELLED) {
34  
35                             queue.removeMin();
36  
37                             continue;  //
No action required, poll queue again
38  
39                         }
40  
41                         currentTime
= System.currentTimeMillis();
42  
43                         executionTime
= task.nextExecutionTime;
44  
45                         if(taskFired
= (executionTime<=currentTime)) {        
//Task的执行时间已到,设置taskFired为true
46  
47                             if (task.period
== 
0)
//
Non-repeating, remove
48  
49                                 queue.removeMin();        //移除队列中的当前任务
50  
51                                 task.state
= TimerTask.EXECUTED;
52  
53                             else //
Repeating task, reschedule
54  
55                                 queue.rescheduleMin(         //重新设置任务的下一次执行时间
56  
57                                   task.period<0 ?
currentTime   - task.period
58  
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这个时间段后执行.

60  
61                             }
62  
63                         }
64  
65                     }
66  
67                     if (!taskFired) //
Task hasn't yet fired; wait
68  
69                         queue.wait(executionTime
- currentTime);    
//还没有执行时间,通过wait等待特定时间
70  
71                 }
72  
73                 if (taskFired)  //
Task fired; run it, holding no locks
74  
75                     task.run();    //已经到达执行时间,执行任务
76  
77             catch(InterruptedException
e) {
78  
79             }
80  
81         }
82  
83 }

也就是说,一旦创建了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)
{
02  
03         if (time
0)
04  
05             throw new IllegalArgumentException("Illegal
execution time."
);
06  
07   
08  
09         synchronized(queue)
{
10  
11             if (!thread.newTasksMayBeScheduled)
12  
13                 throw new IllegalStateException("Timer
already cancelled."
);
14  
15   
16  
17             synchronized(task.lock)
{
18  
19                 if(task.state
!= TimerTask.VIRGIN)             
//我喜欢virgin状态,其他状态表明该Task已经被schedule过了
20  
21                     throw new IllegalStateException(
22  
23                         "Task
already scheduled or cancelled"
);
24  
25                  
26  
27                  //设置Task下一次应该执行的时间,
由System.currentTimeMillis()+/-delay得到
28  
29                 task.nextExecutionTime
= time;              
30  
31                 task.period
= period;
32  
33                 task.state
= TimerTask.SCHEDULED;
34  
35             }
36  
37   
38  
39             queue.add(task);            //queue为TaskQueue类的实例,添加任务到队列中
40  
41             if(queue.getMin()
== task)        
//获取队列中nextExecutionTime最小的任务,如果与当前任务相同
42  
43                 queue.notify();                         //还记得前面看到的queue.wait()方法么
44  
45         }
46  
47 }

不要奇怪,为什么要判断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的实现原理,修改这种实现方式也就非常简单了。

Java Timer&TimerTask原理分析的更多相关文章

  1. Timer&TimerTask原理分析

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

  2. Java Reference核心原理分析

    本文转载自Java Reference核心原理分析 导语 带着问题,看源码针对性会更强一点.印象会更深刻.并且效果也会更好.所以我先卖个关子,提两个问题(没准下次跳槽时就被问到). 我们可以用Byte ...

  3. Java Timer定时器原理

    做项目很多时候会用到定时任务,比如在深夜,流量较小的时候,做一些统计工作.早上定时发送邮件,更新数据库等.这里可以用Java的Timer或线程池实现.Timer可以实现,不过Timer存在一些问题.他 ...

  4. java timer timertask mark

    其实就Timer来讲就是一个调度器,而TimerTask呢只是一个实现了run方法的一个类,而具体的TimerTask需要由你自己来实现,例如这样: 1 2 3 4 5 6 Timer timer = ...

  5. Java 线程池原理分析

    1.简介 线程池可以简单看做是一组线程的集合,通过使用线程池,我们可以方便的复用线程,避免了频繁创建和销毁线程所带来的开销.在应用上,线程池可应用在后端相关服务中.比如 Web 服务器,数据库服务器等 ...

  6. Java Timer, TimerTask, Timer.Schedule

    schedule的意思(时间表.进度表) timer.schedule(new TimerTask(){ void run()},0, 60*60*1000);timer.schedule(new M ...

  7. java string 细节原理分析(2016.5)

    看到了以前2016.5月学习java写的笔记,这里放在一起. String实现的细节原理分析 一.jdk源码中String 的实现 public final class String implemen ...

  8. 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 ...

  9. Java程序运行原理分析

    class文件内容 class文件包含Java程序执行的字节码 数据严格按照格式紧凑排列在class文件的二进制流,中间无分割符 文件开头有一个0xcafebabe(16进制)特殊的标志 JVM运行时 ...

  10. Java 中 ConcurrentHashMap 原理分析

    一.Java并发基础 当一个对象或变量可以被多个线程共享的时候,就有可能使得程序的逻辑出现问题. 在一个对象中有一个变量i=0,有两个线程A,B都想对i加1,这个时候便有问题显现出来,关键就是对i加1 ...

随机推荐

  1. SpringMVC:文件上传和下载

    文件下载 ResponseEntity用于控制器方法的返回值类型,该控制器方法的返回值就是响应到浏览器的响应报文 使用ResponseEntity实现下载文件的功能 @RequestMapping(& ...

  2. .NET 压缩/解压文件

    本文为大家介绍下.NET解压/压缩zip文件.虽然解压缩不是啥核心技术,但压缩性能以及进度处理还是需要关注下,针对使用较多的zip开源组件验证,给大家提供技术选型 目前了解到的常用技术方案有Syste ...

  3. Angular Material 18+ 高级教程 – Custom Themes for Material Design 3 (自定义主题 Material 3)

    v18 更新重要说明 从 Angular Material v18 开始,默认使用的是 Material 3 Design (简称 M3). 而且是正式版,不再是 experimental previ ...

  4. ASP.NET Core – Globalization & Localization

    前言 之前就写过 2 篇, 只是写的很乱, 这篇作为整理版. Asp.net core (学习笔记 路由和语言 route & language) Asp.net core 学习笔记之 glo ...

  5. Figma 学习笔记 – Text

    结构 Figma 的字都会有一个 wrapper 控制 width. 虽然它是看不见的. 但是你要知道它有那个概念存在. 按 T 键, 然后鼠标点击或拉就可以做出一个 text 了. 基本配置 我顺着 ...

  6. SpringMVC —— REST风格简介

    REST风格简介 REST(Representational State Transfer),表现形式转换 传统风格资源描述形式 REST风格描述形式 优点 隐藏资源的访问行为,无法通过地址得知对资源 ...

  7. Scala编程语言基本常识

    ​ 本文参考来源: Scala编程语言基本常识 – 萌面人博客 ​ 一 基本常识 Scala被称为大数据的黄金语言,它的发明人是马丁奥德斯基(Martin Odersky) 2001年诞生,融合了Ja ...

  8. 反问面试官3个ThreadLocal的问题

    ThreadLocal,一个Java人面试绕不开的话题,我也很奇怪为什么那些面试官很喜欢问这个,也不知道他们自己有没有搞清楚. 接下来,我想先说说ThreadLocal的用法和使用场景,然后反问面试官 ...

  9. Vscode 远程切换Python虚拟环境

    在VSCode中远程切换Python虚拟环境是一个涉及多个步骤的过程,包括安装必要的扩展.连接到远程服务器.创建或激活虚拟环境,并在VSCode中选择相应的Python解释器.以下是一个详细的步骤指南 ...

  10. Windows应急响应-QQ巨盗病毒

    目录 病毒背景 样本分析 开启监控 感染病毒 分析病毒行为 C盘文件监控 D盘文件监控 进程监控排查 服务排查 启动项排查 查杀 1.杀掉进程 2.异常服务 3.映像劫持处理 4.hosts文件处理 ...