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

devtools/2024/10/19 3:32:45/

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

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

标准库的Timer

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

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

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

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

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

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

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

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

自己实现定时器

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

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

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

java">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接口的方式重写比较方法,改为按照时间来排。加上第二步逻辑框架的代码后:

java">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();}@Overridepublic int compareTo(MyTimerTask o) {return (int)(o.time - this.time);}
}
class MyTimer{private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();}

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

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

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

java">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;}}});}
}

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

java">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不会释放锁,会影响后续插入操作。

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

java">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();}@Overridepublic 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.ppmy.cn/devtools/91214.html

相关文章

共享`pexlinux`数据文件的网络服务

实验环境准备&#xff1a; 1.红帽7主机 2.要全图形安装 3.配置网络为手动&#xff0c;配置网络可用 4.关闭vmware DHCP功能 一、kickstart自动安装脚本制作 1.安装图形化生成kickstart自动脚本安装工具 2.启动图形制作工具 3.图形配置脚本 这里使用的共享方式是http&#xff0…

Three 【3D车模换肤】

目录 &#x1f31f;前言&#x1f31f;先看效果&#x1f31f;实现代码&#x1f31f;写在最后 &#x1f31f;前言 哈喽小伙伴们&#xff0c;最近工作比较忙一直没有给大家更新&#xff0c;新的专栏 Three.js第三篇&#xff0c;记录一下博主学习Three.js的过程&#xff1b;一起来…

新能源电驱动总成龙头『英搏尔』×企企通,采购数字化助力企业绿色供应链管理

近日&#xff0c;珠海英搏尔电气股份有限公司&#xff08;以下简称“英搏尔”&#xff09;联合企企通成功举办SRM项目启动会&#xff0c;双方企业高层、相关部门负责人及项目组成员参加了此次启动会。 会上&#xff0c;双方就英搏尔的数字化采购管理平台建设方案、实施计划、团…

C++笔试强训11

文章目录 一、选择题1-5题6-10题 二、编程题题目一题目二 一、选择题 1-5题 A. 不是任何一个函数都可定义成内联函数&#xff1a;这是正确的。因为内联函数需要在编译时展开&#xff0c;如果函数体过大或包含复杂的控制结构&#xff08;如循环、递归等&#xff09;&#xff0c…

ArcMap如何将shp数据导入oracle数据库

1. 连接数据库 2.在数据库中新建要素类 3.设置要素类名称和别名以及要素类型 4. 选择该要素类的坐标系 5.导入字段&#xff0c;点击导入&#xff0c;选择shp文件&#xff0c;点击添加&#xff0c;字段就导入进来了&#xff0c;点击完成 6. 点击刚才创建的要素类&#xff0c;点击…

Django学习-数据迁移与数据导入导出

文章目录 一、数据迁移二、数据导入导出1. 数据导出2. 数据导入 一、数据迁移 数据迁移是将项目里定义的模型生成相应的数据表。主要的迁移指令如下&#xff1a; # 第一次生成自定义模型与django admin自带模型迁移文件&#xff0c;后续只生成新增模型迁移文件。后面加App名…

《学会 SpringMVC 系列 · 剖析初始化》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

JavaEE 从入门到精通(二) ~SpringMVC 接收请求和设置响应

晚上好&#xff0c;愿这深深的夜色给你带来安宁&#xff0c;让温馨的夜晚抚平你一天的疲惫&#xff0c;美好的梦想在这个寂静的夜晚悄悄成长。 目录 前言 一、获取请求数据 1. 简单参数 1.1 请求行获取参数 a. 与查询参数的名称相同&#xff0c;底层会自动映射到形参中。 …