JavaEE-多线程编程定时器(多线程完结篇)

news2024/9/21 11:03:38

定时器就是闹钟的效果,指定要一个任务(runnable),指定一个时间,此时这个任务不会立马去执行,而是时间到了才会去执行,这个过程称为——定时执行/延时执行

日常开发中定时执行是一个非常重要的开发组件,比如说短信的验证码是有时效的,这样的效果就可以使用定时器:发送验证码的时候保存一份验证码,当过了规定时间就删除这个验证码。

标准库的Timer

Java标准库的定时器——Timer类

首先实例化一个timer类,然后通过实例对象调用schedule方法可以实现上述操作,可以看到这个方法有两个参数:

第一个参数TimerTask,当我们点进去它的源码可以看到它其实是实现了Runnable接口的,所以就当作runnable使用就可以了。

第二个参数long delay表示“多长时间后执行”,以当前执行schedule的时间为基准,再等delay的时间后进一步执行。

写一段代码感受一下定时执行:

 public static void main(String[] args) {
        Timer timer = new Timer();
       timer.schedule(new TimerTask() {
           @Override
           public void run() {
               System.out.println("delay 3000");
           }
       },3000);
       timer.schedule(new TimerTask() {
           @Override
           public void run() {
               System.out.println("delay 2000");
           }
       },2000);
       timer.schedule(new TimerTask() {
           @Override
           public void run() {
               System.out.println("delay 1000");
           }
       },1000);
    }

写了三段分为delay3~1秒,所以输出顺序是从1000-》3000

可以注意到这个时候任务都执行完了但是进程并没有结束,是因为Timer内部包含了前台线程,组织了进程的结束。

自己实现定时器

真正的学会一个集合框架/类方法的顺序大概为:了解用法->理解原函数的原理->自己动手实现类似的类,所以自己实现一个类方法是特别有用的,可以加深我们对这个类方法的了解。

在实现前应该先构思好自己实现的定时器的框架,我们的需求是(1)能够实现定时执行的效果 (2)能够管理多任务;

第一步:首先我们需要有一个类来表示任务,任务类中要保存执行任务的绝对时间,方便后面线程执行的时候方便判定是否要执行该任务。

class MyTimerTask{
    public Runnable runnable;
    public long time;
    public MyTimerTask(Runnable runnable,long time){
        this.runnable = runnable;
        this.time = time + System.currentTimeMillis();
    }
    void run(){
        runnable.run();
    }
}

上面构造方法中成员变量time的赋值是传入的需要等待的时间加上当前的时间,也就是将执行的绝对时间赋值了,run方法就是来执行当前任务的方法。

第二步:通过数据结构保存多个任务。比较直观的是使用List来保存多个任务,但如果list中的元素也就是任务较多时就要频繁的遍历每一个任务来看是否到了执行时间,我们想要的功能是可以每次访问等待时间最短的元素,如果这个时间最短元素没到时间那么别的肯定也不会到时间,所以可以使用优先级队列创建小根堆实现是最优解

把这些任务保存到优先级队列,按时间的顺序来排,可以做到队首元素就是时间最短的元素。我们想要的比较大小方式是按时间排序,所以可以实现comparable接口的方式重写比较方法,改为按照时间来排。加上第二步逻辑框架的代码后:

class MyTimerTask implements Comparable<MyTimerTask>{
    public Runnable runnable;
    public long time;
    public MyTimerTask(Runnable runnable,long time){
        this.runnable = runnable;
        this.time = time + System.currentTimeMillis();
    }
    void run(){
        runnable.run();
    }

    @Override
    public int compareTo(MyTimerTask o) {
        return (int)(o.time - this.time);
    }
}
class MyTimer{
    private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();

}

第三步:要有一个线程去执行这里的任务,在构造方法中创建一个线程去执行队列中的任务

先创建好框架,我们再进行逐步优化:

先获取队列中的第一个元素看是否到达执行时间,到了就run()然后将该任务从队列中删除,没到就继续循环判定。

class MyTimer{
    private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
    public MyTimer(){
        Thread t = new Thread(()->{
            while (true){
                MyTimerTask task = queue.peek();
                if(task.time <= System.currentTimeMillis() ){
                    task.run();
                    queue.poll();
                }else {
                    continue;
                }

            }

        });
    }
}

第四步:创建一个方法来将所有任务添加进队列

public void MySchedule(Runnable runnable,long time){
        MyTimerTask task = new MyTimerTask(runnable,time);
        queue.offer(task);
    }

第五步:基础框架已经搭建好,接下来进行优化操作:(1)引入锁操作来保证线程安全,将线程中的关键操作加锁 (2)原本的代码中查询队首元素没到执行时间后会继续频繁的循环来检测直到执行时间,是非常消耗资源的,所以在检测没到时间时可以使用wait等待剩余的时间 (3)当任务队列为空时也wait阻塞等待,在队列加入元素后进行notify唤醒。

此处为什么使用wait而不是sleep?

(1)使用sleep的话睡了就真睡了,如果通过interrupt唤醒属于非常规操作 (2)sleep不会释放锁,会影响后续插入操作。

加上刚才的优化操作后整个自己实现的定时器类如下:

class MyTimerTask implements Comparable<MyTimerTask>{
    public Runnable runnable;
    public long time;
    public MyTimerTask(Runnable runnable,long time){
        this.runnable = runnable;
        this.time = time + System.currentTimeMillis();
    }
    void run(){
        runnable.run();
    }

    @Override
    public int compareTo(MyTimerTask o) {
        return (int)(o.time - this.time);
    }
}
class MyTimer{
    private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
    Object locker = new Object();
    public MyTimer(){
        Thread t = new Thread(()-> {
            try {
                while (true) {
                synchronized (locker){
                        if (queue.isEmpty()) {
                            locker.wait();
                        }
                        MyTimerTask task = queue.peek();
                        if (task.time <= System.currentTimeMillis()) {
                            task.run();
                            queue.poll();
                        } else {
                           locker.wait(task.time - System.currentTimeMillis());
                        }
                    }
                }
            }catch (InterruptedException e){
                e.printStackTrace();
            }


        });
    }
    public void MySchedule(Runnable runnable,long time){
        MyTimerTask task = new MyTimerTask(runnable,time);
        queue.offer(task);
        locker.notify();
    }
}

多线程到这就结束了,有对多线程感兴趣的朋友可以看看前几期多线程的内容,感谢观看。

感谢观看

道阻且长,行则将至

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

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

相关文章

Ubuntu 22.04.4LTS 安装 Docker

更新Ubuntu: sudo apt-get update添加Docker库信息&#xff1a; sudo apt install apt-transport-https ca-certificates curl software-properties-common gnupg lsb-release运行下列命令添加Docker官方GPG密钥&#xff1a; sudo curl -fsSL https://mirrors.ustc.edu.cn/d…

Mybatis的基础配置解读,并简单实现增删改查

首先&#xff0c;在了解mybatis的内容之前&#xff0c;我们先学习了解一下mybatis的主配置文件&#xff0c;主配置文件&#xff08;Mybatis-config.xml&#xff09;是我们构建SqlSessionFactory的依据。作为Mybatis的核心内容&#xff0c;其内部标签的顺序&#xff0c;一系列配…

谷粒商城实战笔记-140-商城业务-nginx-搭建域名访问环境二(负载均衡到网关)

文章目录 一&#xff0c;通过域名访问商城架构设计1&#xff0c;为什么nginx要将请求转发给网关2&#xff0c;架构设计 二&#xff0c;配置1&#xff0c;nginx配置1.1 nginx.conf1.2 gulimall.conf1.3 配置原理 2&#xff0c;网关配置 三&#xff0c;记录2个问题1&#xff0c;网…

qt的项目结构

目录 创建新的项目 第一个hell0程序&#xff0c;qt的项目结构 main函数 Widget头文件: pro文件 命名规范 QtCreator 常用快捷键 Qt里边绝大部分的类都是继承自QObject是一个顶层类 父子关系 Qt坐标系 QT常用API函数 对象树 信号和槽机制 自定义信号和槽 自定义信号…

PHP企业培训考试系统小程序源码

&#x1f680;企业培训考试系统&#xff0c;赋能员工成长新引擎&#x1f4da; &#x1f331; 开篇&#xff1a;解锁企业培训新篇章 在快速变化的商业环境中&#xff0c;员工的能力提升是企业持续发展的关键。&#x1f680; 传统的培训方式已难以满足现代企业的需求&#xff0…

CentOS7.9误删了yum,怎么办?

起因是这样的,有人不小心将CentOS7.9的yum插件给删除了,导致了yum命令不能用了。 据他说是执行了这句命令之后,导致的问题。 pm -qa |grep yum | xargs rpm -e --nodeps解决办法 本方法只针对:CentOS Linux release 7.9.2009 (Core)版本生效,其他版本可以参考: https://vau…

Studying-代码随想录训练营day62| Floyd 算法精讲、A*算法精讲(A star算法)、最短路算法总结篇、图论总结

第62天&#xff0c;完结撒花*★,*:.☆(&#xffe3;▽&#xffe3;)/$:*.★* &#xff0c;最后的两个算法学习&#xff0c;编程语言C 目录 Floyd 算法精讲 A*算法精讲&#xff08;A star算法&#xff09; A*算法 复杂度分析 A*算法的缺点 最短路算法总结篇 图论总结…

log4j2: CVE-2021-44228[RCE]

漏洞概述 CVE-2021-44228为Apache Log4j2漏洞&#xff0c;被称为"Log4Shell"。攻击者将恶意代码与服务器进行交互&#xff0c;使服务器日志组件将其记录并执行&#xff0c;从而造就RCE远程代码执行漏洞。 漏洞原理 日志记录机制&#xff1a;Log4j2是一个日志记录工…

【ML】自监督学习 self-supervised Learning

【ML】 自监督学习 1. 自监督学习 bert 为例子1.1 BERT 的特点&#xff1a;1.2 BERT 的模型结构&#xff1a;1.3 训练 BERT 的步骤&#xff1a; 2. how to use BERT![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/f8cccb378f844923a057043fadb60758.png)2. multi-lin…

DL00765-光伏故障检测高分辨率无人机热红外图像细粒度含数据集4000+张

光伏发电作为清洁能源的重要组成部分&#xff0c;近年来得到了广泛应用。然而&#xff0c;随着光伏电站规模的扩大&#xff0c;光伏组件在运行过程中可能会出现各种故障&#xff0c;如热斑、遮挡、接线盒故障等。这些故障不仅会影响光伏电站的发电效率&#xff0c;还可能导致更…

【日常开发】 java返回ECharts数据结构封装

java返回ECharts数据结构封装 一、前端页面示例图如下&#xff1a; 二、准备测试数据&#xff1a; 三、后端 格式封装代码&#xff1a; 四、最终结果&#xff1a; &#x1f388;边走、边悟&#x1f388;迟早会好 一、前端页面示例图如下&#xff1a; 二、准备测试数据&am…

4.11.seq2seq 序列到序列学习

序列到序列学习(seq2seq) ​ 使用两个循环神经网络的编码器和解码器&#xff0c;应用于序列到薛烈类的学习任务。 ​ ​ 在图中&#xff0c;特定的"<eos>"表示序列结束词元。一旦输出序列生成此词元&#xff0c;模型就会停止预测。在循环神经网络解码器的初…

Go语言中gin+gorm开发前端端分离博客时遇到的问题,gorm执行查询时如何选中特定字段?

代码获取 本篇文章的代码放在了Github上&#xff0c;可以免费获取。 https://github.com/zhangdapeng520/zdpgo_gin_examples 概述 在查询用户信息的时候&#xff0c;由于密码这个字段比较敏感&#xff0c;需要进行处理&#xff0c;不要返回给前端。 我一开始的解决方案是直…

统计学:条件概率模型

照片由Edge2Edge Media在Unsplash上拍摄 一、介绍 在概率的许多应用中&#xff0c;不可能直接观察实验的结果&#xff1b;而是观察与结果相关的事件。因此&#xff0c;条件概率模型对于考虑和利用从观察到的事件中获得的信息至关重要。此外&#xff0c;条件概率模型与贝叶斯定理…

【vue3】【elementPlus】【黑暗模式】

从创建vue3项目到引入elementPlus组件并设置黑暗模式 1.创建vue3项目&#xff1a; npm init vuelatest1.1 根据需求定制项目插件&#xff1a; 2.引入elementPlus组件&#xff1a; npm install element-plus --save2.1 如图注册全局elementPlus组件&#xff1a; ------------…

充电不再难,高质量充电体系‘智’领绿色出行新时代

充电不再难&#xff0c;高质量充电体系‘智’领绿色出行新时代 国家发展改革委新闻发言人近日在新闻发布会上郑重声明&#xff0c;将持续强化统筹协调&#xff0c;协同各相关部门加速构建高质量充电基础设施体系&#xff0c;以更有效地满足人民群众对绿色出行的需求。 新能源汽…

C语言:文件处理

文件处理 一、文件的类型&#xff08;一&#xff09;文本文件和二进制文件 &#xff08;二&#xff09;程序文件和数据文件数据文件按照二进制储存 二、文件的打开和关闭&#xff08;一&#xff09;文件指针&#xff08;二&#xff09;文件的打开和关闭1、fopen2、fclose &…

webshell管理工具-中国蚁剑

中国蚁剑 版本说明&#xff1a;中国蚁剑 下载地址&#xff1a;GitHub - AntSwordProject/AntSword-Loader: AntSword 加载器AntSword 加载器. Contribute to AntSwordProject/AntSword-Loader development by creating an account on GitHub.https://github.com/AntSwordProj…

数值分析【2】

目录 第三章 求解三角方程组​编辑 高斯消元​编辑 乘除次数&#xff1a;系数阵k^2,每行系数计算1&#xff0c;右边那列1 乘除总次数&#xff1a;​编辑 平方和 公式 列主元消去法 ​编辑 目的&#xff1a;舍入误差不扩散​编辑 直接LU分解​编辑 改进平方…

C#开发常见面试题三(浅复制和深复制的区别)

C#开发常见面试题三(浅复制和深复制的区别) 一.浅复制和深复制定义 &#xff08;1&#xff09;浅复制&#xff1a;复制一个对象的时候&#xff0c;仅仅复制原始对象中所有的非静态类型成员和所有的引用类型成员的引用。&#xff08;新对象和原对象将共享所有引用类型成员的实…