java的Timer全网最详细总结

news2025/1/27 0:45:42

1.简介

在Java 1.3版本中引入了Timer工具类,它是一个古老的定时器,通常与TimerTaskTaskQueue一起使用。Timer工具类的实现涉及到TimerTask类、Timer类、TimerQueue类和TimerThread类。其中,TimerQueueTimerThread类与Timer类位于同一个类文件中,由Timer类内部调用。

然而,从Java 5开始,在并发包中引入了另一个定时器实现:ScheduledThreadPoolExecutorScheduledThreadPoolExecutorTimer做了许多改进,并提供了更多的功能和工具,可以看作是对Timer的改进和替代。

为什么还要介绍Timer工具类呢?通过了解Timer的功能和它背后的原理,我们可以更好地对比和理解ScheduledThreadPoolExecutor。同时,ScheduledThreadPoolExecutor的一些改进思想在我们平时的编码工作中也可以借鉴。

先画上一张图,描述一下Timer的大致模型,Timer的模型很容易理解,即任务加入到任务队列中,由任务处理线程循环从任务队列取出任务执行

2.主要成员变量

Timer中用到的主要是两个成员变量:

  1. TaskQueue:一个按照时间优先排序的队列,这里的时间是每个定时任务下一次执行的毫秒数(相对于1970年1月1日而言)
  2. TimerThread:对TaskQueue里面的定时任务进行编排和触发执行,它是一个内部无限循环的线程。
//根据时间进行优先排序的队列    
private final TaskQueue queue = new TaskQueue();

//消费线程,对queue中的定时任务进行编排和执行
private final TimerThread thread = new TimerThread(queue);

//构造函数
public Timer(String name) {
        thread.setName(name);
        thread.start();
}

3.定时功能

Timer提供了三种定时模式:

  1. 一次性任务
  2. 按照固定的延迟执行(fixed delay)
  3. 按照固定的周期执行(fixed rate)

第一种比较好理解,即任务只执行一次;针对第一种,Timer提供了以下两个方法:

//在当前时间往后delay个毫秒开始执行
public void schedule(TimerTask task, long delay) {...}
//在指定的time时间点执行
public void schedule(TimerTask task, Date time) {...}

第二种Fixed Delay模式也提供了以下两个方法

//从当前时间开始delay个毫秒数开始定期执行,周期是period个毫秒数
public void schedule(TimerTask task, long delay, long period) {...}
从指定的firstTime开始定期执行,往后每次执行的周期是period个毫秒数
public void schedule(TimerTask task, Date firstTime, long period){...}

它的工作方式是:

第一次执行的时间将按照指定的时间点执行(如果此时TimerThread不在执行其他任务),如有其他任务在执行,那就需要等到其他任务执行完成才能执行。

从第二次开始,每次任务的执行时间是上一次任务开始执行的时间加上指定的period毫秒数。

如何理解呢,我们还是看代码

public static void main(String[] args) {
        TimerTask task1 = new DemoTimerTask("Task1");
        TimerTask task2 = new DemoTimerTask("Task2");
        Timer timer = new Timer();
        timer.schedule(task1, 1000, 5000);
        timer.schedule(task2, 1000, 5000);
}
    
static class DemoTimerTask extends TimerTask {
        private String taskName;
        private DateFormat df = new SimpleDateFormat("HH:mm:ss---");
        
        public DemoTimerTask(String taskName) {
            this.taskName = taskName;
        }
        
        @Override
        public void run() {
            System.out.println(df.format(new Date()) + taskName + " is working.");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println(df.format(new Date()) + taskName + " finished work.");
        }
}

task1和task2是几乎同时执行的两个任务,而且执行时长都是2秒钟,如果此时我们把第六行注掉不执行,我们将得到如下结果(和第三种Fixed Rate模式结果相同):

13:42:58---Task1 is working.
13:43:00---Task1 finished work.
13:43:03---Task1 is working.
13:43:05---Task1 finished work.
13:43:08---Task1 is working.
13:43:10---Task1 finished work.

如果打开第六行,我们再看下两个任务的执行情况。我们是期望两个任务能够同时执行,但是Task2是在Task1执行完成后才开始执行(原因是TimerThread是单线程的,每个定时任务的执行也在该线程内完成,当多个任务同时需要执行时,只能是阻塞了),从而导致Task2第二次执行的时间是它上一次执行的时间(13:43:57)加上5秒钟(13:44:02)。

13:43:55---Task1 is working.
13:43:57---Task1 finished work.
13:43:57---Task2 is working.
13:43:59---Task2 finished work.
13:44:00---Task1 is working.
13:44:02---Task1 finished work.
13:44:02---Task2 is working.
13:44:04---Task2 finished work.

那如果此时还有个Task3也是同样的时间点和间隔执行会怎么样呢?

结论是:也将依次排队,执行的时间依赖两个因素:

1.上次执行的时间

2.期望执行的时间点上有没有其他任务在执行,有则只能排队了


我们接下来看下第三种Fixed Rate模式,我们将上面的代码稍作修改:

public static void main(String[] args) {
        TimerTask task1 = new DemoTimerTask("Task1");
        TimerTask task2 = new DemoTimerTask("Task2");
        
        Timer timer = new Timer();
        timer.scheduleAtFixedRate(task1, 1000, 5000);
        timer.scheduleAtFixedRate(task2, 1000, 5000);
}
    
static class DemoTimerTask extends TimerTask {
        private String taskName;
        private DateFormat df = new SimpleDateFormat("HH:mm:ss---");
        
        public DemoTimerTask(String taskName) {
            this.taskName = taskName;
        }
        
        @Override
        public void run() {
            System.out.println(df.format(new Date()) + taskName + " is working.");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println(df.format(new Date()) + taskName + " finished work.");
        }
}

Task1和Task2还是在相同的时间点,按照相同的周期定时执行任务,我们期望Task1能够每5秒定时执行任务,期望的时间点是:14:21:47-14:21:52-14:21:57-14:22:02-14:22:07,实际上它能够交替着定期执行,原因是Task2也会定期执行,并且对TaskQueue的锁他们是交替着拿的(这个在下面分析TimerThread源码的时候会讲到)

14:21:47---Task1 is working.
14:21:49---Task1 finished work.
14:21:49---Task2 is working.
14:21:51---Task2 finished work.
14:21:52---Task2 is working.
14:21:54---Task2 finished work.
14:21:54---Task1 is working.
14:21:56---Task1 finished work.
14:21:57---Task1 is working.
14:21:59---Task1 finished work.
14:21:59---Task2 is working.
14:22:01---Task2 finished work.

4.TimerThread

上面我们主要讲了Timer的一些主要源码及定时模式,下面我们来分析下支撑Timer的定时任务线程TimerThread。

TimerThread大概流程图如下:

TimerThread流程

源码解释如下:

private void mainLoop() {
        while (true) {
            try {
                TimerTask task;
                boolean taskFired;
                synchronized(queue) {
                    // 如果queue里面没有要执行的任务,则挂起TimerThread线程
                    while (queue.isEmpty() && newTasksMayBeScheduled)
                        queue.wait();
                    // 如果TimerThread被激活,queue里面还是没有任务,则介绍该线程的无限循环,不再接受新任务
                    if (queue.isEmpty())
                        break; 

                    long currentTime, executionTime;
                    // 获取queue队列里面下一个要执行的任务(根据时间排序,也就是接下来最近要执行的任务)
                    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;
                        // taskFired表示是否需要立刻执行线程,当task的下次执行时间到达当前时间点时为true
                        if (taskFired = (executionTime<=currentTime)) {
                            //task.period==0表示这个任务只需要执行一次,这里就从queue里面删掉了
                            if (task.period == 0) { 
                                queue.removeMin();
                                task.state = TimerTask.EXECUTED;
                            } else { // Repeating task, reschedule
                                //针对task.period不等于0的任务,则计算它的下次执行时间点
                                //task.period<0表示是fixed delay模式的任务
                                //task.period>0表示是fixed rate模式的任务
                                queue.rescheduleMin(
                                  task.period<0 ? currentTime   - task.period
                                                : executionTime + task.period);
                            }
                        }
                    }
                    // 如果任务的下次执行时间还没有到达,则挂起TimerThread线程executionTime - currentTime毫秒数,到达执行时间点再自动激活
                    if (!taskFired) 
                        queue.wait(executionTime - currentTime);
                }
                // 如果任务的下次执行时间到了,则执行任务
                // 注意:这里任务执行没有另起线程,还是在TimerThread线程执行的,所以当有任务在同时执行时会出现阻塞
                if (taskFired)  
                    // 这里没有try catch异常,当TimerTask抛出异常会导致整个TimerThread跳出循环,从而导致Timer失效
                    task.run();
            } catch(InterruptedException e) {
            }
        }
}

5.TaskQueue

TaskQueue是Timer类文件中封装的一个队列数据结构,内部默认是一个长度128的TimerTask数组,当任务加入时,检测到数组将满将会自动扩容1倍,并对数组元素根据下次执行时间nextExecutionTime按时间从近到远进行排序。 

```java

void add(TimerTask task) {

    // 检测数组长度,若不够则进行扩容

    if (size + 1 == queue.length)

        queue = Arrays.copyOf(queue, 2*queue.length);

// 任务入队
    queue[++size] = task;

// 排序
    fixUp(size);
}

```
fixUp方法实现:

```java

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;
    }
}

```
TaskQueue中除了fixUp方法外还有一个fixDown方法,这两个其实就是堆排序算法,在算法专题中再进行详细介绍,只要记住他们的任务就是按时间从近到远进行排序,最近的任务排在队首即可。

```java

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 kid

        if (queue[k].nextExecutionTime <= queue[j].nextExecutionTime)

            break;

        TimerTask tmp = queue[j];  queue[j] = queue[k]; queue[k] = tmp;

        k = j;

    }
}

void heapify() {

    for (int i = size/2; i >= 1; i--)

        fixDown(i);

}

```

6.结论

通过上面的分析,我们可以得出以下结论:

  1. Timer支持三种模式的定时任务(一次性任务,Fixed Delay模式,Fixed Rate模式)
  2. Timer中的TimerThread是单线程模式,因此导致所有定时任务不能同时执行,可能会出现延迟
  3. TimerThread中并没有处理好任务的异常,因此每个TimerTask的实现必须自己try catch防止异常抛出,导致Timer整体失效

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1094334.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

docker安装skyWalking笔记

确保安装了docker和docker-compose sudo docker -v Docker version 20.10.12, build 20.10.12-0ubuntu4 sudo docker-compose -v docker-compose version 1.29.2, build unknown 编写docker-compose.yml version: "3.1" services: skywalking-oap:image: apach…

创意作品管理软件 Bridge 2024 mac中文版 br2024功能特色

Bridge 2024 mac旨在连接不同的Ad obe应用程序&#xff0c;帮助用户更轻松地管理和编辑他们的创意作品。 Bridge 2024 mac软件特色和功能介绍 一致的用户体验&#xff1a;Bridge 2024现在具有增强的用户界面&#xff0c;可提供与其他Creative Cloud应用程序类似的体验。用户还…

《向量数据库指南》——向量数据库一些技术难点

一些技术难点 在文章的前半部分,我们列举了一些向量数据库应该具备的特性,然后比较了以 Milvus 为代表的向量数据库和 ANN 算法库、向量检索插件的不同之处。接下来,我们来聊聊构建向量数据库过程中会遇到的一些技术难点。 就好像一架飞机一样,内部每个零部件和系统相互连通…

PPP的建链过程

下图是PPP协议整个链路过程需经历阶段的状态转移图&#xff1a; 图1 PPP链路建立过程 PPP运行的过程简单描述如下&#xff1a; 通信双方开始建立PPP链路时&#xff0c;先进入到Establish阶段。 在Establish阶段&#xff0c;PPP链路进行LCP协商。协商内容包括工作方式是SP&am…

解决win10因为WSL问题无法正常启动docker

解决win10无法成功启动dockerdesktop因为WSL问题无法启动 问题起因解决方法 问题起因 因为需要在windows复现一个CVE漏洞&#xff0c;就需要安装在WIN10上装docker&#xff0c;但是在启动的时候出现下面报错。 然后查了一下是因为WSL的版本太低了。更新以后发现打开docker仍然…

C++20中的关键字

文章目录 前言关键字列表关键字含义总结 前言 源于上一篇中提到了 decltype是一个关键字&#xff0c;所以去查了现有的关键字列表&#xff0c;其实我对C/C的关键字还停留在上世纪大概30多个的时代&#xff0c;虽然知道C加了 auto、constexpr 等关键字&#xff0c;但查询过后才…

52 杨辉三角

杨辉三角 题解1 DP 给定一个非负整数 numRows&#xff0c;生成 杨辉三角的前 numRows 行。 在杨辉三角中&#xff0c;每个数是它左上方和右上方的数的和。 示例 1: 输入: numRows 5 输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]] 示例 2: 输入: numRows 1 输出: [[1…

关于操作系统中对进程管理的认识

文章目录 前言1.计算机组成简单认识2.操作系统1). 操作系统的管理2). 操作系统的目的 3.操作系统中的进程管理1). 进程是什么&#xff1f;2).Linux中的进程 前言 在介绍进程之前&#xff0c;我们需要先了解关于进程的一些边缘的操作系统知识 以及硬件知识&#xff0c;因为操作…

leetCode 1143.最长公共子序列 动态规划 + 图解

此题我的往期文章推荐&#xff1a; leetCode 1143.最长公共子序列 动态规划 滚动数组-CSDN博客https://blog.csdn.net/weixin_41987016/article/details/133689692?spm1001.2014.3001.5501leetCode 1143.最长公共子序列 一步步思考动态规划 优化空间复杂度_呵呵哒(&#xf…

Lua 调试库( debug )

一、调试库作用 使用调试库可以获取 Lua 环境运行过程中的变量和跟踪代码执行。 调试库主要分为两类函数&#xff1a;自省函数和钩子函数 自省函数&#xff1a;允许检查一个正在运行中的程序&#xff0c;例如活动函数的栈、当前正在执行的代码行、局部变量的名称和值。钩子函…

Java多线程悲观锁和乐观锁

悲观锁&#xff1a; 一上来就加锁&#xff0c;没有安全感&#xff0c;每次只能一个线程进入访问完毕后&#xff0c;再解锁。 线程安全&#xff0c;性能较差 乐观锁&#xff1a; 一开始不上锁&#xff0c;认为是没有问题的&#xff0c;大家一起跑&#xff0c;等要出现线程安全问…

SpringBoot--手写组件动态更新@Value的值

原文网址&#xff1a;SpringBoot--手写组件动态更新Value的值_IT利刃出鞘的博客-CSDN博客 简介 本文手写组件&#xff0c;动态更新SpringBoot里Value的值&#xff08;无需重启服务&#xff09;。 不是可以用RefreshScope吗&#xff1f;为什么要手写组件&#xff1f; 动态更…

docker数据卷+挂载(命令讲解+示例)

在容器中管理数据主要有两种方式&#xff1a; 数据卷&#xff08;Volumes&#xff09; 、挂载主机目录 (Bind mounts)。 一、数据卷 数据卷是一个可供一个或多个容器使用的特殊目录&#xff0c;可以在容器之间共享和重用。 特点&#xff1a; 对 数据卷 的修改会立马生效对 …

动态规划简述;斐波那契数列自顶向下和自底向上

概述 动态规划就是把一个问题分解为若干子问题&#xff0c;把子问题的解累加起来&#xff0c;就是当前问题的值。 斐波那契数列&#xff08;自顶向下&#xff09; 一个很好的演示demo&#xff0c; 在进行运算时&#xff0c;要用上备忘录&#xff08;缓存&#xff09;&#x…

从硬件结构到软件

先说说体系冯诺依曼的体系结构&#xff0c;有利于我们后面理解操作系统&#xff0c;软件再怎么发展&#xff0c;也必须遵守硬件的规则。 一 五大硬件理解 如下图: 1 为什么要有输入输出设备 很久以前&#xff0c;我们都是把指令打成孔&#xff0c;有孔无孔表示0,1&#xff0c…

柯桥日常口语学习|生活英语|实用口语口语天天练

1. How far is it from here? 离这儿有多远&#xff1f; 2. Can you give me a hand? 能帮帮我吗&#xff1f; 3. I cant lift my right arm. 我无法举起我的右手臂。 4. This bridge was built two years ago. 这座桥是在两年前建造的。 5. You should eat more. 你应该…

Go语言入门心法(一): 基础语法

Go语言入门心法(一) Go语言入门心法(二): 结构体 Go语言入门心法(三): 接口 一: go语言中变量认知 go语言中变量的定义: &#xff08;要想飞|先会走&#xff09;||&#xff08;翻身仗|抹遗憾 &#xff09; |&#xff08;二八定律&#xff09;(先量变)|(再质变)||&#xf…

vue3实现刻度尺

期望实现效果如下&#xff1a; 一、基本使用 安装slide-ruler&#xff0c;根据文档实现内容 https://github.com/wusb/slide-ruler/blob/master/README-zh_CN.md 二、进一步处理 1、直接复制slide-ruler核心文件&#xff0c;在此基础上进一步处理 处理1&#xff1a;刻度朝向…

思维模型 秩序

本系列文章 主要是 分享 思维模型&#xff0c;涉及各个领域&#xff0c;重在提升认知。秩序是事物正常运行的基石。有序的安排是成功的先决条件。 1 秩序的应用 1.1 秩序在不同科学领域中的应用 物理学和天文学&#xff1a; 物理学家通过研究原子和分子的有序排列来理解物质的…

思维模型 正/反 木桶理论

本系列文章 主要是 分享 思维模型&#xff0c;涉及各个领域&#xff0c;重在提升认知。 1 正/反 木桶理论的应用 1.1 木桶理论的应用 1.1.1 正木桶理论在考试中的应用 小明是一名理科高中生&#xff0c;他在学习过程中发现自己在数理化方面表现较好&#xff0c;但在语文和英…