简单实现Java定时器

news2025/2/28 3:15:23

✨✨hello,愿意点进来的小伙伴们,你们好呐!
🐻🐻系列专栏:【JavaEE】
🐲🐲本篇内容:自己实现Java定时器
🐯🐯作者简介:一名现大二的三非编程小白,日复一日,仍需努力。

  • 1. 什么是定时器
  • 2. Java内置定时器的常用功能
  • 3.自定义定时器
    • 3.1 实现定时器思路
    • 3.2 MyTimerTask 类:
    • 3.3 MyTimer 类:
    • 执行 :
    • 3.4 定时器的问题 :
      • 3.4.1 忙等 :
      • 3.4.2 CPU随机调度导致的bug

1. 什么是定时器

在日常生活中,如果我们想要在 t1 后去做一件重要的事情,那么为了防止忘记,我们就可以使用闹钟的计时器功能,规定了 t1 时间后提醒我们去执行这件事情. — 这就是Java定时器的简单功能


2. Java内置定时器的常用功能

Java中的定时器的类是 : Timer ,为util包中的一个无继承关系的类, 从该类的构造方法中,我们可以使用无参构造器创建该类的对象,也可以在创建类对象的时候指定定时器中所需要的线程的名字,与是否为守护进程
在这里插入图片描述


在定时器中最常用的方法就是 schedule(TimerTask task, long delay)

该方法传参的是一个 TimerTask 对象,与定时器约定的执行时间间隔 delay


而 TimerTask 类则是一个来描述计时器任务的类,该类中有 抽象方法 run(),所以我们给 schedule 传参中的 TimerTask 对象都会重写 run() 方法,然而 重写的run() 方法中的语句,则是定时器需要执行的语句.

在这里插入图片描述


而 delay 则是我们约定从当前时间后的 delay 内执行传入的任务.时间单位为 毫秒


接下来我们来看一个简单的定时器的使用 :

我们在创建定时器的时候指定了定时器中的扫描线程的线程名,然后使用 schedule 方法传入任务与任务执行的间隔时间 2000 毫秒
这个时候在执行该代码的2000毫秒后,定时器就会将该任务执行

public class TimerTest {
    public static void main(String[] args) {
        //创建定时器的时候指定定时器中内置线程的名字
        Timer timer = new Timer("内置线程");

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                //Thread.currentThread().getName() 执行到该任务的线程名
                System.out.println(Thread.currentThread().getName() + " hello");
            }
        },2000);
    }
}


在上述说到有一个定时器内置的扫描线程

这个扫描线程是怎么回事呢?又在定时器中起到什么作用呢?在接下来的自己实现定时器中就会介绍到.

3.自定义定时器

3.1 实现定时器思路

1.实现定时器,我们首先需要有一个可以来描述定时器中的任务的类 MyTimerTask
2.需要使用一个数据结构将定时器中的任务按照执行时间的顺序给组织起来
3.在 MyTimer 定时器类中会有一个线程不断地去访问定时器的任务,查看是否到了指定执行时间.


3.2 MyTimerTask 类:

在MyTimerTask类中,我是这样设计的:
1.实现了Runnable接口与Comparable接口,Comparable是来重新指定比较规则.
2.在类中编写了抽象方法 run(),让创建该类对象的时候可以重新run()方法,用来规划任务内容.
3.nextExecutionTime是来记录当前任务执行的时间是什么时候,并附有nextExecutionTime的set与get方法

import java.util.Comparator;

/**
 * @author 罗鸿基
 * @version 1.0
 * 这个是一个描述线程任务的类
 * 该类中需要有
 */
public abstract class MyTimerTask implements Runnable, Comparable<MyTimerTask> {
    //使用抽象方法 run 方法来让创建该线程的重写 run 方法
    public abstract void run();

    //该任务执行的时间
    protected long nextExecutionTime;

    public MyTimerTask() { }

    public long getNextExecutionTime() {
        return nextExecutionTime;
    }

    public void setNextExecutionTime(long nextExecutionTime) {
        this.nextExecutionTime = nextExecutionTime;
    }

	//因为是使用优先级队列,所以就要重写compareTo方法,制定比较规则
    @Override
    public int compareTo(MyTimerTask o) {
        return (int) (this.getNextExecutionTime() - o.getNextExecutionTime());
    }
}


3.3 MyTimer 类:

MyTimer 类设计如下 :
1.该类中需要有一个用来组织任务的数据结构,我采用优先级阻塞队列来实现组织任,因为PriorityBlockingQueue 实现了BlockingQueue接口,可以当为线程安全的队列,而我们需要对每一个任务按照执行时间的顺序进行排序.所以我选择PriorityBlockingQueue来实现该功能
2.在该类中,我们需要有一个线程,不断地对任务队列中优先级最高(最快执行)的任务进行查看, 看是否到达执行时间.
3.在schedule方法中,我们需要修改任务中的执行时间,并将任务插入任务队列

package csdn;

import java.util.concurrent.PriorityBlockingQueue;

/**
 * @author 罗鸿基
 * @version 1.0
 * 这个是定时器类
 * 需要有一个用来组织任务的数据结构 -- PriorityBlockingQueue
 */
public class MyTimer {
    //该队列用来组织任务
    //因为有可能有多个线程去访问任务队列,使用就使用PriorityBlockingQueue (阻塞队列)
    //因为也需要判断任务的优先级(执行的时间间隔差异),所以我们所以优先级队列
    private PriorityBlockingQueue<MyTimerTask> queue = new PriorityBlockingQueue<>();
	//扫描线程
    private Thread thread;

    public MyTimer() {
        //该线程用来不断扫描任务队列中是否有可执行的任务
        thread = new Thread(() -> {
            while (true) {
                try {
                    //取出当前队列中执行时间间隔最短的任务
                    //如果队列中没有任务,那么就一直等待
                    MyTimerTask take = queue.take();
                    //取出任务执行的时间
                    long nextExecutionTime = take.getNextExecutionTime();
                    //取出系统当前的时间
                    long newTime = System.currentTimeMillis();
                    //两个时间对比,看是否到达执行时间
                    if (nextExecutionTime > newTime) {
                        //未到执行时间
                        //将任务重新放入队列
                        queue.put(take);
                    }else {
                        //到达执行时间,就执行任务
                        take.run();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        //启动扫描线程
        thread.start();
    }

    public void schedule(MyTimerTask task,long delay) {
        //以防别的线程同时往定时器中插入任务
        synchronized (queue) {
            //修改任务中的时间
            task.setNextExecutionTime(System.currentTimeMillis() + delay);
            //将任务插入队列
            queue.put(task);
        }
    }
}


执行 :

public class TimerTest {
    public static void main(String[] args) {

        MyTimer myTimer = new MyTimer();
        myTimer.schedule(new MyTimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " 延迟1000毫秒的任务 hello");
            }
        },1000);

        myTimer.schedule(new MyTimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " 延迟2000毫秒的任务 hello");
            }
        },2000);

        myTimer.schedule(new MyTimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " 延迟3000毫秒的任务 hello");
            }
        },3000);
    }
}


执行结果符合我们的预期
在这里插入图片描述


我们可以看到当前Java进程中的确有一个线程一直在工作,该线程就是扫描线程

在这里插入图片描述


且当前线程是属于等待状态

在这里插入图片描述


但是的确的定时器其实是有不小的问题的,我们下面来分析问题并解决

3.4 定时器的问题 :

3.4.1 忙等 :

如果队列中没有任务,那么取出任务会就一直等待,那么就还不会造成忙等,但是如果是队列中有任务的话,当前线程就会不断循环,取出任务来进行查看.这种行为是很浪费CPU资源的,下面我们来进行优化

在这里插入图片描述


在这里插入图片描述


在这里插入图片描述


3.4.2 CPU随机调度导致的bug

对于多线程代码中,CPU的随机调度是万恶之源,会导致很多意想不到的bug出现.


下面我们来举个例子来解释一下CPU随机调度带来的bug :

例如 : 在某一时刻线程线程从队列中取出任务的执行时间为14:00(下图中红色的的线所指向),然后这个时候扫描线程被操作系统调度后不再继续执行
而此时又有一个线程去调用了schedule() 方法,加入一个任务,这个任务的执行时间为13:30.然后加入任务队列,执行notify()
那么这个时候操作系统再继续调度到取出任务后,发现距离任务执行的时间还有一段距离,这个时候就调用wait(time)方法
然后接下来没有任务加入了,就不会调用notify()方法,那么线程就加入等待状态,等待到14.00再唤醒,那么这个时候就会完美地错过了新加入线程的执行时间

在这里插入图片描述


图解:
在这里插入图片描述


代码实现:

public MyTimer() {
        //该线程用来不断扫描任务队列中是否有可执行的任务
        thread = new Thread(() -> {
            while (true) {
                synchronized (lock) {
                    try {
                        //取出当前队列中执行时间间隔最短的任务
                        MyTimerTask take = queue.take();
                        //取出任务执行的时间
                        long nextExecutionTime = take.getNextExecutionTime();
                        //取出系统当前的时间
                        long newTime = System.currentTimeMillis();
                        //两个时间对比,看是否到达执行时间
                        if (nextExecutionTime > newTime) {
                            //未到执行时间
                            //将任务重新放入队列
                            queue.put(take);
                            lock.wait(nextExecutionTime - newTime);
                        } else {
                            //到达执行时间,就执行任务
                            take.run();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

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

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

相关文章

【手写 Vue2.x 源码】第九篇 - 对象数据变化的观测情况

一&#xff0c;前言 上篇&#xff0c;主要介绍了数组深层观测的实现&#xff0c;核心几个点如下&#xff1a; 最初仅对数组类型进行了原型方法重写&#xff0c;并未进行递归处理&#xff0c;所以&#xff0c;当时仅实现了数组的单层劫持&#xff1b; 通过对数组进行 observe…

二、Gtk4-GtkApplication and GtkApplicationWindow

1 GtkApplication 1.1 GtkApplication and g_application_run 人们编写编程代码来开发应用程序。什么是应用程序?应用程序是使用库运行的软件&#xff0c;其中包括操作系统、框架等。在GTK 4编程中&#xff0c;GTK应用程序是使用GTK库运行的程序(或可执行程序)。 编写GtkAp…

信创改造,你了解多少?

最近&#xff0c;“信创”一词在IT圈瞬间爆火&#xff0c;那什么是信创&#xff1f;又能带来哪些突破性的改变&#xff1f;跟随佑友一起来详细了解一下… 信创的来源 2016年3月4日&#xff0c;24家专业从事软硬件关键技术研究及应用的国内单位&#xff0c;共同发起成立了一个非…

分布式链路追踪-skywalking入门体验

背景 旁友&#xff0c;你的线上服务是不是偶尔来个超时&#xff0c;或者突然抖动一下&#xff0c;造成用户一堆反馈投诉。然后你费了九牛二虎之力&#xff0c;查了一圈圈代码和日志才总算定位到问题原因了。或者公司内部有链路追踪系统&#xff0c;虽然可以很轻松地通过监控判…

deb dpkg fpm cpack debmake 打包

文章目录deb 简介hello debfpmpreinst postinst prerm postrmcmake cpackdebmakedeb 简介 deb: Linux发行版Debian系列(如Debian, Ubuntu等)的软件包格式, 没有自提取(Self-extracting), 不能直接运行, 需要借助dpkg等安装. Dpkg: Debian Package Manager, Debian包管理器, 中…

Python读取各种形式文件(excel,txt),python基本用法

读取excel,读取结果是dataframe形式。 excelFile ranalyze_search_category.xlsx df pd.DataFrame(pd.read_excel(excelFile)) print(df) 详情&#xff1a;(21条消息) 在Python中使用Pandas.DataFrame对Excel操作笔记一 - 从Excel里面获取说需要的信息_fengqiaoxian的博客-CS…

TensorFlow之模型保存与加载

模型在训练过程中或者在训练之后&#xff0c;模型的执行过程能被保存&#xff0c;也就是&#xff0c;模型能从暂停中恢复以免训练的时间过长。因此&#xff0c;被保存的模型可以被共享&#xff0c;其他人可以重新构建相同的模型。被保存的模型以如下的两种方式进行共享&#xf…

青训营——前端方向练习题(不定项选择题例题)

文章目录 &#x1f4c4;前言 PART1 PART2 PART3 PART4 PART5 PART6 PART7 PART8 PART9 &#x1f4c4;前言 一共有十八题&#xff0c;题目选项为不定项&#xff0c;有单选&#xff0c;也有多选。 PART1 选择题 1&#xff1a; 下列哪些是 HTML5 的新特性&#xff1f; A…

Android 深入系统完全讲解(3)

3 Zygote 虚拟机的流程&#xff0c;学习方法 说完了 init 的启动过程&#xff0c;我们来说说 Zygote 的启动过程。 这里我们看下整个的步骤&#xff0c;主要完成了&#xff1a; 1 startVM() 创建虚拟机 2 startReg() 注册 JNI 方法 3 preload()预加载通用类&#xff0c;这里主…

【信管7.1】质量与质量管理过程

质量与质量管理过程对于我们的项目管理理论相关的学习来说&#xff0c;质量是除了范围、进度、成本之外的另一个核心内容。还记得我们在学习敏捷的时候讲过的项目管理三角形吗&#xff1f;通过之前的课程&#xff0c;我们已经学完了它的三个支点。接下来&#xff0c;我们就要学…

播客丨关于年终总结,程序员有话说

绘声绘影绘声绘影是网易云信独家打造的一档聚焦行业热点、个人成长方面的播客栏目。栏目希望通过邀请不同背景、不同行业、不同阅历的企业研发、产品、运营等相关岗位负责人作为节目嘉宾&#xff0c;以自身职业视角交流行业洞见和发展前景&#xff1b;以过来人的视角分享在时代…

dfs、bfs搜索题型小结

一、全排列 &#xff08;1&#xff09;1199&#xff1a;全排列 原题链接 解析 &#xff08;2&#xff09;剪枝思想 满足等式关系的全排列——dfs剪枝 &#xff08;3&#xff09;P1088 [NOIP2004 普及组] 火星人 原题链接 解析 二、组合&#xff08;选与不选&#xff09;…

web(四)—— CSS基础(选择器进阶、Emmet语法、背景属性、元素显示模式、三大特性)

一、选择器进阶目标&#xff1a;能够理解 复合选择器 的规则&#xff0c;并使用 复合选择器 在 HTML 中选择元素1. 复合选择器1.1 后代选择器&#xff1a;空格作用&#xff1a;根据 HTML 标签的嵌套关系&#xff0c;选择父元素 后代中 满足条件的元素 选择器语法&#xff1a;选…

Maven的安装配置与基本使用

Maven简介&#xff1a; Maven是专门用于管理和构建java项目的工具&#xff0c;它的主要功能有&#xff1a; 提供了一套标准化的项目结构标准化的项目结构&#xff1a; Maven提供了一套标准化的项目结构&#xff0c;所有的IDE使用Maven构建的项目结构完全一样&#xff0c;所有…

【IEEE出版社】人工智能、数据挖掘、机器人、传感等领域SCI,自引率低,对国人友好,评职毕业高分好刊~

1区人工智能类SCI&EI 【出版社】IEEE 【自引率】4.30%&#xff08;低&#xff09; 【国人占比】13.40% 【期刊简介】IF:6.5-7.0&#xff0c;JCR1区&#xff0c;中科院3区 【检索情况】SCI&EI 双检&#xff0c;正刊 【参考周期】3-5个月左右录用 【截稿日期】202…

如何彻底关闭Win10自动更新,Win10永久关闭自动更新的方法

如何彻底关闭Win10自动更新&#xff1f;Win10自动更新的问题是很多用户都遇到的问题&#xff0c;很多时候我们关闭了自动更新&#xff0c;过一段时间系统又自动更新了&#xff0c;由于win10自动更新非常顽固&#xff0c;所以我们要从多个地方下手才能永久关闭其自动更新&#x…

Java中几种常量池的区分

文章目录前言了解一下 ldc 指令字符串常量池在 Java 内存区域的哪个位置1.全局字符串池&#xff08;string pool也有叫做string literal pool&#xff09;2.class文件常量池&#xff08;class constant pool&#xff09;3.运行时常量池&#xff08;runtime constant pool&#…

干货 | Python的面试题目+答案合集

作为一个 Python 新手&#xff0c;你必须熟悉基础知识。 在本期内容中我们将讨论一些 Python 面试的基础问题和高级问题以及答案&#xff0c;以帮助你完成面试。 包括 Python 开发问题、编程问题、数据结构问题、和 Python 脚本问题。 接下来让我们来深入研究这些问题 Pytho…

AD转换芯片精度计算及校正方法

文章目录前言一、转换精度二、重要参数1.线性误差&#xff08;INL&#xff09;和差分线性误差&#xff08;DNL&#xff09;2.失调误差和增益误差三、转换校正总结前言 本文对模数转换芯片的精度进行简要介绍&#xff0c;帮助大家正确选型&#xff0c;并介绍了一个基本的ADC转换…

postgresql13+postgis3.2安装教程

postgresql13postgis3.2安装教程 安装postgresql13 安装pg13 pg13安装包 cd /home/soft/pg tar -zxvf postgresql-13.6.tar.gz cd postgresql-13.6# yum -y install -y readline-devel./configure --prefix/usr/local/pgsqlmake -j4make install设置环境变量 vim /etc/pr…