java定时器-Timer和TimerTask详解

article/2025/8/30 10:13:45

1、例子入手

package pers.growing.test;import java.util.Timer;
import java.util.TimerTask;public class Main {/*** 延迟100ms后,间隔1s打印出:hello world** @param args* @throws InterruptedException*/public static void main(String[] args) throws InterruptedException {Timer t = new Timer();t.scheduleAtFixedRate(new TimerTask() {@Overridepublic void run() {System.out.println("hello world");}}, 100, 1000);}}

结果:

hello world
hello world
hello world
hello world
hello world
hello world
hello world

2、类讲解

TimerTask.java:主要为任务的具体内容。

Timer.java中含有3个类:Timer、TimerThread、TaskQueue。

三者关系为:TaskQueue中存放一些列将要执行的TimerTask,以数组的形式存放,下标约小(注:下标为0不处理,即使用的最小下标为1),则表明优先级越高。

TimerThread为Thread的扩展类,会一直从TaskQueue中获取下标为1的TimerTask进行执行。并根据该TimerTask是否需要重复执行来决定是否放回到TaskQueue中。

Timer用于配置用户期望的任务执行时间、执行次数、执行内容。它内部会配置TimerThread、TaskQueue。

3、源码解读

Timer类下的方法如下:

Timer中涉及到4个成员变量,queue、thread已经在上面介绍过了,对于nextSerialNumber,只是用于命名默认的thread名称使用。threadReaper为了在GC时进行相关处理,后面再介绍。

Timer的构造函数实现大同小异,以Timer(String,boolean)为例:

  public Timer(String name, boolean isDaemon) {thread.setName(name); //设置成员变量的线程名称thread.setDaemon(isDaemon); //该线程是否为守护线程thread.start();//起线程}

schedule()以schedule(TimerTask,long,long)为例:

 public void schedule(TimerTask task, long delay, long period) {if (delay < 0)throw new IllegalArgumentException("Negative delay.");if (period <= 0)throw new IllegalArgumentException("Non-positive period.");sched(task, System.currentTimeMillis()+delay, -period); //最后调用了sched方法}

这一块有困惑的可能是为什么period取了负数?而且所有的schedule(...)方法,最后传给sched中的period都是负的。之后再介绍。

scheduleAtFixedRate()以scheduleAtFixedRate(TimerTask,long,long)为例:

    public void scheduleAtFixedRate(TimerTask task, long delay, long period) {if (delay < 0)throw new IllegalArgumentException("Negative delay.");if (period <= 0)throw new IllegalArgumentException("Non-positive period.");sched(task, System.currentTimeMillis()+delay, period); //也是调用sched方法}

此时会发现schedule与scheduleAtFixedRate似乎区别不大,唯一有区别的是schedule最后传值给sched的period是负数,而scheduleAtFixedRate传的是正数。

在schedule方法的注释上,有这段内容:

     * <p>In fixed-delay execution, each execution is scheduled relative to* the actual execution time of the previous execution.  If an execution* is delayed for any reason (such as garbage collection or other* background activity), subsequent executions will be delayed as well.* In the long run, the frequency of execution will generally be slightly* lower than the reciprocal of the specified period (assuming the system* clock underlying <tt>Object.wait(long)</tt> is accurate).

翻译:如果出现某一次任务的延迟,那么之后的任务都会以period为周期进行延迟。

而scheduleAtFixedRate方法也有对应注释:

     * <p>In fixed-rate execution, each execution is scheduled relative to the* scheduled execution time of the initial execution.  If an execution is* delayed for any reason (such as garbage collection or other background* activity), two or more executions will occur in rapid succession to* "catch up."  In the long run, the frequency of execution will be* exactly the reciprocal of the specified period (assuming the system* clock underlying <tt>Object.wait(long)</tt> is accurate).

翻译:每次的执行都是以初始时计算好的时间为准,如果出现某次任务的延迟,则之后的任务会快速执行,即按计划时间执行。

那么看下Sched()方法实现:

 private void sched(TimerTask task, long time, long period) {//接收具体任务,第一次执行时间,周期if (time < 0)throw new IllegalArgumentException("Illegal execution time.");// Constrain value of period sufficiently to prevent numeric// overflow while still being effectively infinitely large.if (Math.abs(period) > (Long.MAX_VALUE >> 1))period >>= 1;synchronized(queue) {if (!thread.newTasksMayBeScheduled)throw new IllegalStateException("Timer already cancelled.");synchronized(task.lock) {if (task.state != TimerTask.VIRGIN)throw new IllegalStateException("Task already scheduled or cancelled");task.nextExecutionTime = time; //给TimerTask赋值task.period = period;task.state = TimerTask.SCHEDULED;}queue.add(task);//将TimerTask放到队列中,并进行队列排序if (queue.getMin() == task)//如果队列里恰好下标为1的任务为当前的task,则直接唤醒queue.notify();}}

queue中的add和getMin操作如下:

    void add(TimerTask task) {// Grow backing store if necessaryif (size + 1 == queue.length)queue = Arrays.copyOf(queue, 2*queue.length);queue[++size] = task;fixUp(size);//让task进行排序,排序并不是十分严谨,将nextExecutionTime较小的往前排}private void fixUp(int k) {while (k > 1) {int j = k >> 1;if (queue[j].nextExecutionTime <= queue[k].nextExecutionTime)break;TimerTask tmp = queue[j];  queue[j] = queue[k]; queue[k] = tmp;k = j;}}TimerTask getMin() { //注意最小的值是从下标为1的获取,queue[0]其实没用到return queue[1];}

数据已经放到queue中了,那么看下是什么时候执行的。在之前Timer的构造函数这块,有一句是:thread.start();说明TimerThread在Timer初始化之后就一直启用着,那看下它的处理。

 public void run() {try {mainLoop(); //主要实现内容} finally {// Someone killed this Thread, behave as if Timer cancelledsynchronized(queue) {newTasksMayBeScheduled = false;queue.clear();  // Eliminate obsolete references}}}/*** 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-empty//如果队列为空并且是有标志位,则等待。没有标志位的情况为不在需要执行timer了,比如cancel或被gc的时候while (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();//获取就近的tasksynchronized(task.lock) {//如果该task已经被置为cancelled,则将它从队列里面移出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) { // period表示该task是一次性的,用完就移出queue.removeMin();//移出task,这块会有queue的重新排序task.state = TimerTask.EXECUTED;//更新状态为执行中} else {//可重复执行的task操作,将重新计算下次执行时间,并重新排序    //重点,此处解释为什么period分正负:区别schedule方法和scheduleAtFixedRate//如果是负数,则以当前时间为准,往后计算下次执行时间//如果是正数,则以理论时间为准,往后计算下次执行时间queue.rescheduleMin(task.period<0 ? currentTime   - task.period: executionTime + task.period);}}}if (!taskFired) // 如果还没到任务执行时间就处于等待queue.wait(executionTime - currentTime);}if (taskFired)  // 到执行时间了//执行task中的run方法,而不是start方法,所以并不是另起一个线程进行操作task.run();} catch(InterruptedException e) {//如果是不能捕获的异常,就会有风险了}}}

关于queue中的removeMin()和rescheduleMin()方法如下:

    void removeMin() {//前两行赋值可能会将queue乱序,所以才会有fixDown重新排序queue[1] = queue[size];queue[size--] = null;  // Drop extra reference to prevent memory leakfixDown(1);}//因为取的时候也是取下标为1的task进行操作,所以此次也是将下标为1的task重新赋时间,并排序void rescheduleMin(long newTime) {queue[1].nextExecutionTime = newTime;fixDown(1);}//和fixUp方法类似,该排序单独看也并非严谨,但由于每次操作都会经历,所以应该是准的吧!private void fixDown(int k) {int j;while ((j = k << 1) <= size && j > 0) {if (j < size &&queue[j].nextExecutionTime > queue[j+1].nextExecutionTime)j++; // j indexes smallest kidif (queue[k].nextExecutionTime <= queue[j].nextExecutionTime)break;TimerTask tmp = queue[j];  queue[j] = queue[k]; queue[k] = tmp;k = j;}}

当timerTask调用了timerTask.cancel()操作时,也可以人为的将它从Timer队列中清除掉,方法如下:

 public int purge() {int result = 0;synchronized(queue) {for (int i = queue.size(); i > 0; i--) {if (queue.get(i).state == TimerTask.CANCELLED) {queue.quickRemove(i); //由于i取值时必然大于0,所以肯定能够找到正常的数据result++;}}if (result != 0)queue.heapify();//重新排序}return result;}

queue中的quickRemove和heapify方法:

    void quickRemove(int i) { //只要简单赋值就行了,最后排序交给heapify()assert i <= size;queue[i] = queue[size];queue[size--] = null;  // Drop extra ref to prevent memory leak}void heapify() {for (int i = size/2; i >= 1; i--)fixDown(i); //在前面的篇幅中介绍过了}

当然如果Timer也可以人为的取消,不在继续定时任务。其方法如下:

   public void cancel() {synchronized(queue) {thread.newTasksMayBeScheduled = false;queue.clear(); //会将队列中的所有的task赋值为nullqueue.notify();  // 通知thread中的mainLoop进行break操作}}

综上,其实大部分流程就屡清楚了。顺带介绍下Timer中的threadReaper。代码如下:

   /*** This object causes the timer's task execution thread to exit* gracefully when there are no live references to the Timer object and no* tasks in the timer queue.  It is used in preference to a finalizer on* Timer as such a finalizer would be susceptible to a subclass's* finalizer forgetting to call it.*/private final Object threadReaper = new Object() {protected void finalize() throws Throwable {synchronized(queue) {thread.newTasksMayBeScheduled = false;queue.notify(); // In case queue is empty.}}};

重写了finalize方法,该方法为GC时候调用,主要使Timer中的thread能够优雅退出。

4、总结

优势:可以实现比较轻量级的定时任务,而且很方便

劣势:

①由于Timer只是创建了一个thread去执行queue中的task,那么就可能会出现上一个任务执行延迟了,会影响到下一个定时任务。

②在TimerThread#mainloop中,也可看到,如果抛出了InterruptedException之外无法捕获到的异常时,mainloop就会中断,Timer也就无法使用了。

 

          


http://chatgpt.dhexx.cn/article/rmfSaB4G.shtml

相关文章

定时器Timer与TimerTask的使用

一&#xff1a;简介 在JDK类库中Timer主要负责计划任务的功能&#xff0c;也就是在指定时间执行某一任务&#xff0c;执行时候会在主线程之外起一个单独的线程执行指定的任务。该类主要是设置任务计划&#xff0c;但封装的类是TimerTask类。 TimerTask是一个实现了Runnable接口…

Java的定时器Timer和定时任务TimerTask应用以及原理简析

记录&#xff1a;272 场景&#xff1a;Java JDK自带的定时器Timer和定时任务TimerTask应用以及原理简析。在JDK工具包&#xff1a;java.util中可以找到源码&#xff0c;即java.util.Timer和java.util.TimerTask。TimerTask实现Runnable接口的run方法。Timer的属性TimerThread …

原码、补码、反码的转换

1.原码 &#xff08;1&#xff09;一个正数&#xff0c;他的原码就是它对应的二进制数字。 &#xff08;2&#xff09;一个负数&#xff0c;按照绝对值大小转换成的二进制数&#xff0c;然后最高位补1&#xff0c;就是负数的原码。 2.反码 &#xff08;1&#xff09;正数的反…

原码、补码、反码的关系

一. 机器数和真值 在学习原码, 反码和补码之前, 需要先了解机器数和真值的概念. 1、机器数 一个数在计算机中的二进制表示形式, 叫做这个数的机器数。机器数是带符号的&#xff0c;在计算机用一个数的最高位存放符号, 正数为0, 负数为1. 比如&#xff0c;十进制中的数 3 &…

原码,反码,补码,阶码,移码

本文转载自本站大佬“不去上课”&#xff0c;原文链接https://blog.csdn.net/ruidianbaihuo/article/details/87875178 原码&#xff0c;反码&#xff0c;补码&#xff0c;阶码&#xff0c;移码是什么?有什么区别(讨论机器数的表示) 本文内容参考自王达老师的《深入理解计算机…

原码、反码、补码之间的转换和简单运算

一、正整数的原码、反码、补码完全一样&#xff0c;即符号位固定为0&#xff0c;数值位相同 二、负整数的符号位固定为1&#xff0c;由原码变为补码时&#xff0c;规则如下&#xff1a; 1、原码符号位1不变&#xff0c;整数的每一位二进制数位求反&#xff0c;得到反码 2、反码…

原码,反码,补码的概念

计算机里都是以补码的形式存储数据&#xff0c;电脑只能识别二进制的0和1&#xff0c; 一个字节&#xff08;8位&#xff09;为例 原码 &#xff1a;最高位符号位&#xff0c;0代表正数&#xff0c;1代表负数&#xff0c;非符号位为该数字绝对值的二进制。 反码&#xff1a;正…

C语言——原码, 反码, 补码 详解

目录 一. 机器数和真值 1、机器数 2、真值 二. 原码, 反码, 补码的基础概念和计算方法. 1. 原码 2. 反码 3. 补码 三. 为何要使用原码, 反码和补码 四 原码, 反码, 补码 再深入 同余的概念 负数取模 开始证明 一. 机器数和真值 在学习原码, 反码和补码之前, 需要先…

原码、反码、补码、移码的概念及转换

目录 前言 1.原码 2.反码 3.补码 4.移码 前言 学习完数的小数点表示&#xff0c;下一个需要解决的问题就是数的机器码表示问题。 在计算机中对数据进行运算操作时&#xff0c;符号位应该如何表示&#xff1f;是否也同数值位一道参加运算操作呢&#xff1f;如果参加&…

mt7620芯片处理器核心资料

MT7620产品系统整合了2T2R 802.11n Wi-Fi 收发器、580MHz MIPS 24KEc™ 中央处理器 (CPU)、5 端口高速以太网络端口物理层 (Ethernet PHY)、HNAT、存储器加速器、USB2.0 主机/设备&#xff0c;以及多种慢速输入输出 (U客论坛)。MT7620A 支持 PCIe、RGMII&#xff0c;适用于 AC7…

mt7620参考设计原理图,mt7620芯片资料,mt7620处理器资料

mt7620参考设计原理图,芯片资料,处理器资料 核心资料 芯片处理器资料 设计注意事项 处理器大全 音频语音设计资料文档(U客论坛) MT7620产品系统整合了2T2R 802.11n Wi-Fi 收发器、580MHz MIPS 24KEc™ 中央处理器 (CPU)、5 端口高速以太网络端口物理层 (Ethernet PHY)、HNAT…

MT7682参考手册,MT7682芯片资料介绍

MT7682 Reference Manual 2.总线体系结构与内存映射 MediaTek MT7682采用32位多AHB矩阵&#xff0c;为物联网和可穿戴设备提供低功耗、快速、灵活的数据操作.表2.1-1显示了总线主机之间的互连(Cortex-M4&#xff0c;四个spi主站&#xff0c;spi从机&#xff0c;调试系统&#…

MT7628 openwrt学习(1)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、拿到板子之后干的事情二、openwrt编译文件下载tftp中的坑 最后是SSH和简单的编译 前言 主要是用来记录我的MT7628踩坑经历&#xff0c;所有东西都是开源的…

mt7621芯片更换ram

最近公司设备的内存占用率过高&#xff0c;经常性的导致设备挂掉&#xff0c;于是准备换一个ram。 解决方案&#xff1a; 在openwrt目录下&#xff0c;进入target/linux/ramips/dts目录&#xff0c;找到设备使用的CPU型号对应的dts文件&#xff0c;然后修改其中内容。将memory的…

MT7621处理器资料解析,MT7621数据表

MT7621处理器资料解析,MT7621数据表 MT7621 Wi-Fi 系统单芯片包含功能强大的 880 MHz MIPS 1004KEc™ 双核心中心处理器 (CPU)、5 端口 Gigabit 以太网络交换器,以及RGMII、PCIe、USB、SD-XC 等众多连接选项。这款全新系统单芯片亦随附我们经现场验证的硬件支持,涵盖网络地…

MT6261处理器参数介绍,MT6261芯片资料

MT6261处理器&#xff1a; MT6261是一种基于低功耗CMOS工艺的集成前沿电源管理单元、模拟基带和无线电电路的单片芯片。 MT6261是一种功能丰富、功能非常强大的用于高端GSM/GPRS能力的单芯片解决方案。基于32位ARM7EJ-S TM RISC处理器&#xff0c;MT6261的卓越处理能力TH高带…

MT8167处理器型号对比,MT8167芯片资料介绍

MT8167平台有两个版本&#xff0c;分别是MT8167A和MT8167B。两者之间最大的、唯一的区别在于MT8167A提供略高的处理和图形性能规格&#xff0c;最大的区别是支持60pps的1080p视频解码&#xff08;MT8167B的30fps以上&#xff09;和全高清支持&#xff08;19201200&#xff09; …

mt7682芯片处理器详细资料介绍

MTK MT7682S是基于一个高度集成的芯片组&#xff0c;包括一个微控制器单元(MCU)、一个低功耗的1x11n单波段Wi-Fi子系统和一个电源管理单元(PMU)。单片机是一个带有浮点单元的ARM Cortex-M4处理器&#xff0c;与1MB的闪存集成在一起。 Wi-Fi子系统包含802.11b/g/n无线电、基带和…

MT7621A的首板终于收到了

经过一个近一个月的生产加工&#xff0c;MT7621A的硬件终天收到了。 先上两个图&#xff1a; 正面 刚拿到电路板&#xff0c;测试电源时竞然发现1.1VD对地短路。&#xff08;一想这个可完了&#xff0c;刚做的电路板就短路&#xff0c;一定死定了&#xff09; 通过认真仔细的分…

MT7628 wifi模块,MTK路由器芯片介绍

MT7628处理器&#xff1a; MT7628nn/mt7628an 系列产品是新一代2T2R 802.11n Wi-Fi AP / 路由器 (系统单芯片)。 MT7628可提升射频效能表现、减低功耗&#xff0c;并将整体物料清单 (BOM) 成本优化&#xff0c;令它成为性价比最出众的 2x2 11n 解决方案。MT7628产品家族整合了…