当前位置: 首页 > news >正文

多线程——定时器

定时器在日常开发中常用到的组件工具,类似于“闹钟”
设定一个时间,到了时间定时器就会自动去执行某个逻辑

Java标准库,也提供了定时器的实现

Timer timer = new Timer( );

代码演示:

public class Test14 {public static void main(String[] args) {Timer timer = new Timer();timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("三秒");}},3000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("两秒");}},2000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("一秒");}},1000);System.out.println("main");}
}

运行结果:

main
一秒
两秒
三秒

由此可见,定时器不是按照代码的先后顺序执行的,而是按照给定时间的先后顺序执行的

如何手动实现定时器?

手动实现的MyTimer里面要包含哪些内容
1.需要一个线程,负责掐时间,等到任务到了执行时间,该线程就会负责执行
2.还需要一个队列/数组,能够保存所有的schedule进来的任务
每个任务都带有等待时间delay,所以使用优先级队列,等待时间delay短的先执行

首先要创建一个类,用来描述一个任务
该类中要有执行时间和要执行的代码

class MyTimerTask implements Comparable<MyTimerTask> {// 在什么时间点来执行这个任务.// 此处约定这个 time 是一个 ms 级别的时间戳.private long time;// 实际任务要执行的代码.private Runnable runnable;public long getTime() {return time;}// delay 期望是一个 "相对时间"public MyTimerTask(Runnable runnable, long delay) {this.runnable = runnable;// 计算一下真正要执行任务的绝对时间. (使用绝对时间, 方便判定任务是否到达时间的)this.time = System.currentTimeMillis() + delay;}public void run() {runnable.run();}@Overridepublic int compareTo(MyTimerTask o) {return (int) (this.time - o.time);// return (int) (o.time - this.time);}
}

然后把每个任务都放进优先级队列里面进行比较,任务等待时间短的先执行

class MyTimer {// 负责扫描任务队列, 执行任务的线程.private Thread t = null;// 任务队列private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();public void schedule(Runnable runnable, long delay) {MyTimerTask task = new MyTimerTask(runnable, delay);queue.offer(task);}}

取出队头,使用线程去执行

public MyTimer() {t = new Thread(() -> {// 扫描线程就需要循环的反复的扫描队首元素, 然后判定队首元素是不是时间到了.// 如果时间没到, 啥都不干// 如果时间到了, 就执行这个任务并且把这个任务从队列中删除掉.while (true) {try {while (queue.isEmpty()) {// 暂时先不处理}MyTimerTask task = queue.peek();// 获取到当前时间long curTime = System.currentTimeMillis();if (curTime >= task.getTime()) {// 当前时间已经达到了任务时间, 就可以执行任务了.queue.poll();task.run();} else {// 当前时间还没到, 暂时先不执行// 不能使用 sleep. 会错过新的任务, 也无法释放锁.// Thread.sleep(task.getTime() - curTime);}}} catch (InterruptedException e) {e.printStackTrace();}}});// 要记得 start !!!!t.start();}

代码还存在线程安全问题,对代码进行优化:
完整代码:

import java.util.PriorityQueue;// 通过这个类, 来描述一个任务
class MyTimerTask implements Comparable<MyTimerTask> {// 在什么时间点来执行这个任务.// 此处约定这个 time 是一个 ms 级别的时间戳.private long time;// 实际任务要执行的代码.private Runnable runnable;public long getTime() {return time;}// delay 期望是一个 "相对时间"public MyTimerTask(Runnable runnable, long delay) {this.runnable = runnable;// 计算一下真正要执行任务的绝对时间. (使用绝对时间, 方便判定任务是否到达时间的)this.time = System.currentTimeMillis() + delay;}public void run() {runnable.run();}@Overridepublic int compareTo(MyTimerTask o) {return (int) (this.time - o.time);// return (int) (o.time - this.time);}
}// 通过这个类, 来表示一个定时器
class MyTimer {// 负责扫描任务队列, 执行任务的线程.private Thread t = null;// 任务队列private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();// 搞个锁对象, 此处使用 this 也可以.private Object locker = new Object();public void schedule(Runnable runnable, long delay) {synchronized (locker) {MyTimerTask task = new MyTimerTask(runnable, delay);queue.offer(task);// 添加新的元素之后, 就可以唤醒扫描线程的 wait 了.locker.notify();}}public void cancel() {// 结束 t 线程即可// interrupt}// 构造方法. 创建扫描线程, 让扫描线程来完成判定和执行.public MyTimer() {t = new Thread(() -> {// 扫描线程就需要循环的反复的扫描队首元素, 然后判定队首元素是不是时间到了.// 如果时间没到, 啥都不干// 如果时间到了, 就执行这个任务并且把这个任务从队列中删除掉.while (true) {try {synchronized (locker) {while (queue.isEmpty()) {// 暂时先不处理locker.wait();}MyTimerTask task = queue.peek();// 获取到当前时间long curTime = System.currentTimeMillis();if (curTime >= task.getTime()) {// 当前时间已经达到了任务时间, 就可以执行任务了.queue.poll();task.run();} else {// 当前时间还没到, 暂时先不执行// 不能使用 sleep. 会错过新的任务, 也无法释放锁.// Thread.sleep(task.getTime() - curTime);locker.wait(task.getTime() - curTime);}}} catch (InterruptedException e) {e.printStackTrace();}}});// 要记得 start !!!!t.start();}
}public class ThreadDemo31 {public static void main(String[] args) {MyTimer timer = new MyTimer();timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello 3000");}}, 3000);timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello 2000");}}, 2000);timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello 1000");}}, 1000);}
}

运行结果:

hello 1000
hello 2000
hello 3000

相关文章:

  • mac 使用brew卸载node
  • Oracle AWR报告的生成和解读
  • 【全】OpenSSL创建生成CA证书、服务器、客户端证书及密钥说明
  • 蓝桥杯备赛 | 洛谷做题打卡day2
  • Protobuf小记(万字)
  • 基于FPGAWS2812B的贪吃蛇方案设计(含源码)
  • pod 控制器
  • 移动安全-certutil
  • .net core IResultFilter 的 OnResultExecuted和OnResultExecuting的区别
  • 封装日期时间组件
  • 边缘计算的挑战和机遇(结合RDH-EI)
  • 12GoF之代理模式
  • Hutool sqlserver 数据库简单操作-Db
  • Linux 【C编程】IO进阶— 阻塞IO、非阻塞IO、 多路复用IO、 异步IO
  • 阶段七第二章 性能测试工具
  • 《Javascript数据结构和算法》笔记-「字典和散列表」
  • 【css3】浏览器内核及其兼容性
  • 【跃迁之路】【477天】刻意练习系列236(2018.05.28)
  • android百种动画侧滑库、步骤视图、TextView效果、社交、搜房、K线图等源码
  • Angular 响应式表单之下拉框
  • Druid 在有赞的实践
  • Effective Java 笔记(一)
  • ES6 学习笔记(一)let,const和解构赋值
  • interface和setter,getter
  • iOS仿今日头条、壁纸应用、筛选分类、三方微博、颜色填充等源码
  • Java深入 - 深入理解Java集合
  • leetcode378. Kth Smallest Element in a Sorted Matrix
  • Linux学习笔记6-使用fdisk进行磁盘管理
  • Linux中的硬链接与软链接
  • Material Design
  • MySQL的数据类型
  • Nodejs和JavaWeb协助开发
  • ViewService——一种保证客户端与服务端同步的方法
  • 动态规划入门(以爬楼梯为例)
  • 诡异!React stopPropagation失灵
  • 少走弯路,给Java 1~5 年程序员的建议
  • 深入浏览器事件循环的本质
  • 思否第一天
  • 吐槽Javascript系列二:数组中的splice和slice方法
  • 微信小程序填坑清单
  • 译自由幺半群
  • 原生 js 实现移动端 Touch 滑动反弹
  • 走向全栈之MongoDB的使用
  • (3)nginx 配置(nginx.conf)
  • (LNMP) How To Install Linux, nginx, MySQL, PHP
  • (Spark3.2.0)Spark SQL 初探: 使用大数据分析2000万KF数据
  • (SpringBoot)第二章:Spring创建和使用
  • (规划)24届春招和25届暑假实习路线准备规划
  • (牛客腾讯思维编程题)编码编码分组打印下标题目分析
  • (学习日记)2024.03.12:UCOSIII第十四节:时基列表
  • (转)VC++中ondraw在什么时候调用的
  • .cn根服务器被攻击之后
  • .NET Core 和 .NET Framework 中的 MEF2
  • .net mvc 获取url中controller和action
  • @cacheable 是否缓存成功_让我们来学习学习SpringCache分布式缓存,为什么用?