多线程案例(3)-定时器

文章目录

  • 多线程案例三
  • 三、 定时器

大家好,我是晓星航。今天为大家带来的是 多线程案例三 相关的讲解!😀

多线程案例三

三、 定时器

定时器是什么

定时器也是软件开发中的一个重要组件. 类似于一个 “闹钟”. 达到一个设定的时间之后, 就执行某个指定 好的代码.

定时器是一种实际开发中非常常用的组件.

比如网络通信中, 如果对方 500ms 内没有返回数据, 则断开连接尝试重连.

比如一个 Map, 希望里面的某个 key 在 3s 之后过期(自动删除).

类似于这样的场景就需要用到定时器.

这个方法的效果是,给定时器,注册一个任务。任务不会立即执行,而是在指定时间进行执行。

标准库中的定时器

  • 标准库中提供了一个 Timer 类. Timer 类的核心方法为schedule.
  • schedule 包含两个参数. 第一个参数指定即将要执行的任务代码, 第二个参数指定多长时间之后 执行 (单位为毫秒).
System.out.println("程序启动");
//这个 Timer 类就是标准库的定时器
Timer timer = new Timer();
timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("运行定时器任务1");}
},1000);
timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("运行定时器任务2");}
},2000);
timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("运行定时器任务3");}
},3000);

实现定时器

定时器的构成:

  • 一个带优先级的阻塞队列

为啥要带优先级呢?

因为阻塞队列中的任务都有各自的执行时刻 (delay). 最先执行的任务一定是 delay 最小的. 使用带 优先级的队列就可以高效的把这个 delay 最小的任务找出来.

  • 队列中的每个元素是一个 Task 对象.
  • Task 中带有一个时间属性, 队首元素就是即将
  • 同时有一个 worker 线程一直扫描队首元素, 看队首元素是否需要执行

1)Timer 类提供的核心接口为 schedule, 用于注册一个任务, 并指定这个任务多长时间后执行.

public class Timer {public void schedule(Runnable command, long after) {// TODO}
}

2)Task 类用于描述一个任务(作为 Timer 的内部类). 里面包含一个 Runnable 对象和一个 time(毫秒时 间戳)

这个对象需要放到 优先队列 中. 因此需要实现 Comparable 接口.

static class Task implements Comparable<Task> {private Runnable command;private long time;public Task(Runnable command, long time) {this.command = command;// time 中存的是绝对时间, 超过这个时间的任务就应该被执行this.time = System.currentTimeMillis() + time;}public void run() {command.run();}@Overridepublic int compareTo(Task o) {// 谁的时间小谁排前面return (int)(time - o.time);}}
}

3)Timer 实例中, 通过 PriorityBlockingQueue 来组织若干个 Task 对象.

通过 schedule 来往队列中插入一个个 Task 对象.

class Timer {// 核心结构private PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue();public void schedule(Runnable command, long after) {Task task = new Task(command, after);queue.offer(task);}    
}

4)Timer 类中存在一个 worker 线程, 一直不停的扫描队首元素, 看看是否能执行这个任务

所谓 “能执行” 指的是该任务设定的时间已经到达了.

class Timer {// ... 前面的代码不变public Timer() {// 启动 worker 线程Worker worker = new Worker();worker.start();}class Worker extends Thread{@Overridepublic void run() {while (true) {try {Task task = queue.take();long curTime = System.currentTimeMillis();if (task.time > curTime) {// 时间还没到, 就把任务再塞回去
queue.put(task);} else {// 时间到了, 可以执行任务
task.run();}} catch (InterruptedException e) {e.printStackTrace();break;}}}}
}

但是当前这个代码中存在一个严重的问题, 就是 while (true) 转的太快了, 造成了无意义的 CPU 浪费.

比如第一个任务设定的是 1 min 之后执行某个逻辑. 但是这里的 while (true) 会导致每秒钟访问队 首元素几万次. 而当前距离任务执行的时间还有很久呢.

5)引入一个 mailBox 对象, 借助该对象的 wait / notify 来解决 while (true) 的忙等问题.

class Timer {// 存在的意义是避免 worker 线程出现忙等的情况private Object mailBox = new Object(); 
}

修改 Worker 的 run 方法, 引入 wait, 等待一定的时间.

public void run() {while (true) {try {Task task = queue.take();long curTime = System.currentTimeMillis();if (task.time > curTime) {// 时间还没到, 就把任务再塞回去queue.put(task);// [引入 wait] 等待时间按照队首元素的时间来设定. synchronized (mailBox) {// 指定等待时间 waitmailBox.wait(task.time - curTime);}} else {// 时间到了, 可以执行任务task.run();}} catch (InterruptedException e) {e.printStackTrace();break;}}
}

修改 Timer 的 schedule 方法, 每次有新任务到来的时候唤醒一下 worker 线程. (因为新插入的任务可能 是需要马上执行的).

public void schedule(Runnable command, long after) {Task task = new Task(command, after);queue.offer(task);// [引入 notify] 每次有新的任务来了, 都唤醒一下 worker 线程, 检测下当前是否有synchronized (mailBox) {mailBox.notify();}
}

完整代码

/**
* 定时器的构成:
* 一个带优先级的阻塞队列
* 队列中的每个元素是一个 Task 对象.
* Task 中带有一个时间属性, 队首元素就是即将
* 同时有一个 worker 线程一直扫描队首元素, 看队首元素是否需要执行
*/
public class Timer {static class Task implements Comparable<Task> {private Runnable command;private long time;public Task(Runnable command, long time) {this.command = command;// time 中存的是绝对时间, 超过这个时间的任务就应该被执行this.time = System.currentTimeMillis() + time;}public void run() {command.run();}@Overridepublic int compareTo(Task o) {// 谁的时间小谁排前面return (int)(time - o.time);}}// 核心结构private PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue();// 存在的意义是避免 worker 线程出现忙等的情况private Object mailBox = new Object();class Worker extends Thread{@Overridepublic void run() {while (true) {try {Task task = queue.take();long curTime = System.currentTimeMillis();if (task.time > curTime) {// 时间还没到, 就把任务再塞回去
queue.put(task);synchronized (mailBox) {// 指定等待时间 wait
mailBox.wait(task.time - curTime);}} else {// 时间到了, 可以执行任务
task.run();}} catch (InterruptedException e) {e.printStackTrace();break;}}}}public Timer() {// 启动 worker 线程Worker worker = new Worker();worker.start();}// schedule 原意为 "安排"public void schedule(Runnable command, long after) {Task task = new Task(command, after);queue.offer(task);synchronized (mailBox) {mailBox.notify();}}public static void main(String[] args) {Timer timer = new Timer();Runnable command = new Runnable() {@Overridepublic void run() {System.out.println("我来了");timer.schedule(this, 3000);}};timer.schedule(command, 3000);}
}

我们自己写的定时器:

咱们的定时器里面,核心

1.要有一个扫描线程,负责判定时间到/执行任务

2.还要有一个数据结构,来保存所有被注册的任务

我们在当前场景下,使用优先级队列,是一个很好的选择!!!

按照时间小的,作为优先级高的,此时队首元素就是整个队列中,最先要执行的任务。此时扫描线程只需扫一下队首元素即可。不必遍历整个队列。(如果队首元素还没到执行时间,后续元素更不可能到时间!!!)

此时我们自己写的定时器基本框架就已经搭构完成,我们用MyTask这个类来创建定义要执行的任务runnable和时间戳time,而后在MyTimer中使用他们

阻塞队列,只能先把元素出队列,才好判定,不满足还得塞回去。这不像普通队列,可以直接取队首元素判定的。

定时器(自己版本)完整版:

import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.PriorityBlockingQueue;
/*** Created with IntelliJ IDEA.* Description:* User: 晓星航* Date: 2023-07-29* Time: 11:20*/
//使用这个类来表示一个定时器中的任务.
class MyTask implements Comparable<MyTask>{//要执行的任务内容private Runnable runnable;//任务啥时候执行,(使用毫秒时间戳表示)private long time;public MyTask(Runnable runnable, long time) {this.runnable = runnable;this.time = time;}//获取当前任务的时间public long getTime() {return time;}//执行任务public void run() {runnable.run();}@Overridepublic int compareTo(MyTask o) {//返回小于0 大于0  0//this 比 o 小,返回 < o//this 比 o 小,返回 > o//this 和 o 相等,返回 = o//当前要实现的效果,是队首元素 是时间最小的任务//这俩谁减谁不要去记!!!试试就知道了。//要么是 this.time - o.time   要么是 o.time - this.timereturn (int)(this.time - o.time);}
}//自己写的简单的定时器
class MyTimer {//扫描线程private Thread t = null;//有一个阻塞优先级队列,来保存任务。private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();public MyTimer() {t = new Thread(()->{while (true) {try {//取出队首元素,检查看看队首元素任务是否到时间了。//如果时间没到,就把任务塞回队列里去。//如果时间到了,就把任务进行执行。synchronized (this) {MyTask myTask = queue.take();long curTime = System.currentTimeMillis();if (curTime < myTask.getTime()) {//还没到点,先不必执行queue.put(myTask);//在 put 之后,进行一个 waitthis.wait(myTask.getTime() - curTime);} else {//时间到了!!执行任务!!myTask.run();}}} catch (InterruptedException e) {e.printStackTrace();}}});t.start();}//指定两个参数//第一个参数是 任务 内容//第二个参数是 任务 在多少毫秒之后执行,形如 1000public void schedule(Runnable runnable,long after) {//注意这里的时间上的换算MyTask task = new MyTask(runnable,System.currentTimeMillis() + after);queue.put(task);synchronized (this) {this.notify();}}
}
public class ThreadDemo25 {public static void main(String[] args) {MyTimer myTImer = new MyTimer();myTImer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("任务1");}},1000);myTImer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("任务2");}},2000);myTImer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("任务3");}},3000);}
}

从运行结果不难看出,我们自己写的定时器和API自带的Timer是一样的,都会按照对应的时间进行启动。

感谢各位读者的阅读,本文章有任何错误都可以在评论区发表你们的意见,我会对文章进行改正的。如果本文章对你有帮助请动一动你们敏捷的小手点一点赞,你的每一次鼓励都是作者创作的动力哦!😘

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

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

相关文章

视频太大怎么压缩变小?三招教会你压缩视频

如果视频文件太大&#xff0c;不仅占用空间&#xff0c;还不方便传输&#xff0c;这时候就需要我们对视频进行压缩处理&#xff0c;目前市面上有多种视频压缩软件&#xff0c;想要压缩率高&#xff0c;又保留原视频的画质&#xff0c;可以参考以下的几个方法。 一、嗨格式压缩大…

K8s中的Controller

Controller的作用 &#xff08;1&#xff09;确保预期的pod副本数量 &#xff08;2&#xff09;无状态应用部署 &#xff08;3&#xff09;有状态应用部署 &#xff08;4&#xff09;确保所有的node运行同一个pod&#xff0c;一次性任务和定时任务 1.无状态和有状态 无状态&…

python excel 操作

excel文件内容如下&#xff1a; 一、xlrd 读Excel 操作 1、打开Excel文件读取数据 filexlrd.open_workbook(filename)#文件名以及路径&#xff0c;如果路径或者文件名有中文给前面加一个 r 2、常用函数 &#xff08;1&#xff09;获取一个sheet工作表 table file.sheets(…

大模型使用——超算上部署LLAMA-2-70B-Chat

大模型使用——超算上部署LLAMA-2-70B-Chat 前言 1、本机为Inspiron 5005&#xff0c;为64位&#xff0c;所用操作系统为Windos 10。超算的操作系统为基于Centos的linux&#xff0c;GPU配置为A100&#xff0c;所使用开发环境为Anaconda。 2、本教程主要实现了在超算上部署LLAM…

【Linux】在服务器上创建Crontab(定时任务),自动执行shell脚本

业务场景&#xff1a;该文即为上次编写shell脚本的姊妹篇,在上文基础上,将可执行的脚本通过linux的定时任务自动执行,节省人力物力,话不多说,开始操作! 一、打开我们的服务器连接工具 连上服务器后,在任意位置都可以执行:crontab -e 如果没有进入编辑cron任务模式 根据提示查看…

【TypeScript】中定义与使用 Class 类的解读理解

目录 类的概念类的继承 &#xff1a;类的存取器&#xff1a;类的静态方法与静态属性&#xff1a;类的修饰符&#xff1a;参数属性&#xff1a;抽象类&#xff1a;类的类型: 总结&#xff1a; 类的概念 类是用于创建对象的模板。他们用代码封装数据以处理该数据。JavaScript 中的…

Benchmarking Augmentation Methods for Learning Robust Navigation Agents 论文阅读

论文信息 题目&#xff1a;Benchmarking Augmentation Methods for Learning Robust Navigation Agents: the Winning Entry of the 2021 iGibson Challenge 作者&#xff1a;Naoki Yokoyama, Qian Luo 来源&#xff1a;arXiv 时间&#xff1a;2022 Abstract 深度强化学习和…

day50-springboot+ajax分页

分页依赖&#xff1a; <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.0.0</version> </dependency> 配置&#xff1a; …

uniapp使用阿里图标

效果图&#xff1a; 前言 随着uniApp的深入人心&#xff0c;我司也陆续做了几个使用uniapp做的移动端跨平台软件&#xff0c;在学习使用的过程中深切的感受到了其功能强大和便捷&#xff0c;今日就如何在uniapp项目中使用阿里字体图标的问题为大家献上我的一点心得&#xff0…

webpack基础知识五:说说Loader和Plugin的区别?编写Loader,Plugin的思路?

一、区别 前面两节我们有提到Loader与Plugin对应的概念&#xff0c;先来回顾下 loader 是文件加载器&#xff0c;能够加载资源文件&#xff0c;并对这些文件进行一些处理&#xff0c;诸如编译、压缩等&#xff0c;最终一起打包到指定的文件中plugin 赋予了 webpack 各种灵活的…

Android AccessibilityService研究

AccessibilityService流程分析 AccessibilityService开启方式AccessibilityService 开启原理 AccessibilityService开启方式 . 在Framework里直接添加对应用app 服务component。 loadSetting(stmt, Settings.Secure.ACCESSIBILITY_ENABLED,1); loadSetting(stmt, Settings.Se…

Vue3 Script Setup 速查表

微信搜索 【大迁世界】, 我会第一时间和你分享前端行业趋势&#xff0c;学习途径等等。 本文 GitHub https://github.com/qq449245884/xiaozhi 已收录&#xff0c;有一线大厂面试完整考点、资料以及我的系列文章。 快来免费体验ChatGpt plus版本的&#xff0c;我们出的钱 体验地…

如何在PCB设计过程中处理好散热

在现代高性能电子设备中&#xff0c;散热是一个常见而重要的问题。正确处理散热问题对于确保电子设备的可靠性、稳定性和寿命至关重要。 下面将介绍在PCB设计过程中处理散热问题的方法和技巧&#xff0c;以帮助大家提高设计质量和性能。 首先&#xff0c;在处理散热问题之前&…

Webpack怎么使用?

Webpack 使用 前几篇文章中已经介绍了如何初始化包管理器 package.json 这里不再重复介绍&#xff0c;如有需要请查看 搭建工程化项目。 安装 :::warning 注意 请确保你已经安装了 yarn&#xff0c;如有需要请查看 搭建工程化开发环境。 ::: 通过命令 yarn add webpack web…

Vue系列第八篇:echarts绘制柱状图和折线图

本篇将使用echarts框架进行柱状图和折线图绘制。 目录 1.绘制效果 2.安装echarts 3.前端代码 4.后端代码 1.绘制效果 2.安装echarts // 安装echarts版本4 npm i -D echarts4 3.前端代码 src/api/api.js //业务服务调用接口封装import service from ../service.js //npm …

windows永久关闭更新

不要去services.msc 服务里面关闭windowUpdata了&#xff0c;对win11和部分win10根本不管用&#xff0c;下面在教你一招永久关闭&#xff08;原理不是关闭&#xff0c;只是延长更新时间&#xff0c;时间可以设置百年后&#xff0c;所以和关闭差不多&#xff09; windows图形化…

LeetCode--HOT100题(22)

目录 题目描述&#xff1a;160. 相交链表&#xff08;简单&#xff09;题目接口解题思路代码 PS: 题目描述&#xff1a;160. 相交链表&#xff08;简单&#xff09; 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表…

[C++从入门到精通] 9.inline、const、mutable、this和static

&#x1f4e2;博客主页&#xff1a;https://loewen.blog.csdn.net&#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;本文由 丶布布原创&#xff0c;首发于 CSDN&#xff0c;转载注明出处&#x1f649;&#x1f4e2;现…

Day 75:通用BP神经网络 (2. 单层实现)

代码&#xff1a; package dl;import java.util.Arrays; import java.util.Random;/*** Ann layer.*/ public class AnnLayer {/*** The number of input.*/int numInput;/*** The number of output.*/int numOutput;/*** The learning rate.*/double learningRate;/*** The m…

极海APM32F003F6P6烧写问题解决记录

工作中遇到的&#xff0c;折腾了好久&#xff0c;因为电脑重装过一遍系统&#xff0c;软件也都重新安装了&#xff0c;所以不知道之前的配置是什么&#xff0c;旧项目代码编译没问题&#xff0c;烧写时疯狂报错&#xff0c;用的是JLink。 keil版本v5.14 win10版本 JLink版本…