【Java多线程】案例(4):定时器

目录

一、定时器是什么?

二、Java标准库中的定时器

三、自己实现定时器

四、标准库中更推荐使用的定时器


一、定时器是什么?

定时器是一种用于在指定时间间隔或特定时间点执行特定任务的工具或设备。在计算机科学中,定时器通常是软件或硬件组件,用于跟踪时间的流逝并在预定的时间触发事件或执行操作

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

定时器的使用场景:

  • 定时提醒:日历应用程序或提醒应用程序可以使用定时器来触发提醒事件,例如在预定的时间点提醒用户参加会议或生日。

  • 定时任务:定时关闭电视或空调等家用电器,以减少不必要的能源消耗。

  • 游戏开发:在游戏开发中,定时器可以用于实现游戏中的动画效果、计时器功能或限时任务等。

二、Java标准库中的定时器

Java标准库中提供了一个 Timer 类,Timer 类的核心方法为 schedule。

schedule 方法包含两个参数。

  • 第一个参数:指定即将要执行的任务代码,这个参数的类型 TimerTask 是一个抽象类,它实现了 Runnable 接口,就把它当作 Runnable 来使用即可。
  • 第二个参数:指定多长时间之后执行任务(单位为毫秒)。

Timer 类内部包含一个线程,只有这个线程来执行所有的定时任务。这意味着如果有多个任务被安排在同一时间执行,它们将按顺序逐个执行,而不能并行执行。

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

这段代码创建了一个 Timer 对象,并安排了三个定时任务,分别在延迟 3000 毫秒、2000 毫秒和 1000 毫秒后执行。

由于 Timer 内部包含了前台线程,因此进程没有结束。

三、自己实现定时器

需求:

  1. 能够延时执行任务/指定时间执行任务。
  2. 能够管理多个任务。

对于要延时执行的任务,要将其转换成绝对时间(当前的时间戳),这样方便判定后续任务是否要执行,因为如果还是记录延时,需要随着时间的推移不断更新delay,非常麻烦。

  • 我们将任务及任务执行的绝对时间封装成一个类Task,更具体地表示这个任务。
  • 用一个优先级队列保存这些任务,以任务执行的绝对时间靠前为优先级。(不使用 PriorityBlockingQueue,在这个实现中容易死锁)
  • 定时器中需要有线程一直扫描队首元素,看队首是否需要执行。

详细过程见代码:

import java.util.PriorityQueue;//用于描述一个任务的类
class MyTimerTask implements Comparable<MyTimerTask> {//要执行的任务private Runnable runnable;//当前任务实际执行的时间戳private long time;public MyTimerTask(Runnable runnable, long delay) {this.runnable = runnable;//取当前时刻的时间戳+delay(延迟时间),作为当前任务实际执行的时间戳this.time = System.currentTimeMillis() + delay;}public void run() {this.runnable.run();}public long getTime() {return this.time;}@Overridepublic int compareTo(MyTimerTask o) {//试试哪个是升序就可以return (int) (this.time - o.time);//return (int) (o.time - this.time);}
}//定时器
class MyTimer {//用优先级队列存放所有任务,以时间戳升序排序private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();//定时器中存在一个工作线程,不停扫描队首元素,看是否能执行这个任务public MyTimer() {Thread t = new Thread(() -> {while (true) {try {synchronized (this) {//任务队列为空,主动阻塞等待if (queue.isEmpty()) {this.wait();}//看队首元素是否到达要执行的时间MyTimerTask task = queue.peek();long curTime = System.currentTimeMillis();//已经到达(当前时间的时间戳更大),任务执行并出队if (curTime >= task.getTime()) {task.run();queue.poll();} else {//队首还没到达执行时间,则任务队列所有任务都还没到达执行时间//避免重复循环判断,主动阻塞等待(等待的最长时间就是当前的时间间隔)this.wait(task.getTime() - curTime);}}} catch (InterruptedException e) {throw new RuntimeException();}}});t.start();}//安排指定的任务,在指定的时间之后执行public void schedule(Runnable runnable, long delay) {synchronized (this) {MyTimerTask task = new MyTimerTask(runnable, delay);queue.offer(task); //加入到任务队列//有新任务,就唤醒上次阻塞等待的任务//当前新任务的实际执行时间可能更早,此时再判断队首任务是否执行,并更新wait的时间this.notify();}}
}//实现定时器
public class Demo2 {public static void main(String[] args) {MyTimer myTimer = new MyTimer();myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello 3000");}}, 3000);myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello 2000");}}, 2000);myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello 1000");}}, 1000);}
}

执行结果于使用标准库的一致。这里不使用PriorityBlockingQueue的原因是:它只能处理队列为空时候的阻塞,而任务还都未到执行时间时的阻塞,就需要通过额外的锁对象和 wait 来实现。

此时代码就更复杂了,引入了两把锁(额外引入的锁对象和阻塞队列自带的锁),这就容易死锁了,而我们自己控制wait,只需要一把锁,更容易控制代码。

四、标准库中更推荐使用的定时器

由于 Timer 是单线程的,因此不推荐在任务中执行耗时操作或阻塞操作,因为这会影响到其他任务的执行。另外,如果在任务中抛出未捕获的异常,会导致该线程终止,从而影响到其他任务的执行。

Java标准库中还有一种主要的定时器实现,就是使用案例(3)中标准库的线程池 Executors 工厂类里的第四个工厂方法:Executors.newScheduledThreadPool(int corePoolSize)。

这个方法返回ScheduledExecutorService,ScheduledExecutorService 是一种特殊类型的线程池,它具有定时执行任务的功能。它继承自 ExecutorService 接口,同时扩展了定时执行任务的能力。相比于 Timer 类,ScheduledExecutorService 支持更多的灵活性和功能,并且可以更好地处理多个并发任务。

 ScheduledExecutorService 接口的核心方法也是 schedule,与 Timer 不同的是其参数。

  1. 参数 command 是一个 Runnable 对象,表示要执行的任务;
  2. 参数 delay 是延迟时间,以指定的时间单位(TimeUnit)表示;
  3. 参数 unit 则是时间单位,可以是纳秒、毫秒、秒等。
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;public class Demo3 {public static void main(String[] args) {//使用 Executors.newScheduledThreadPoolScheduledExecutorService threadPool = Executors.newScheduledThreadPool(1);threadPool.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello 3000");}}, 3000, TimeUnit.MILLISECONDS);threadPool.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello 2000");}}, 2000, TimeUnit.MILLISECONDS);threadPool.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello 1000");}}, 1000, TimeUnit.MILLISECONDS);threadPool.shutdown();}
}

 

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

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

相关文章

React+TS项目搭建

使用webpack5搭建ReactTS项目 一.初始化项目 初始化一个基本的reactts项目,首先创建一个项目文件夹,输入初始化命令 npm init -y 初始化完成后生成package.json文件,之后需要在项目下新增以下所示目录结构和文件 ├── build | ├── webpack.base.js # 公共配置 | ├…

造数据,别慌!使用python faker创建大批量随机测试数据

前言 在数据库的使用过程中&#xff0c;有很多场合是要自行去构造大规模数据&#xff0c;以供测试、性能功能验证使用。我在前边一文&#xff1a;PostgreSQL - 大规模随机数据生成方法里介绍了PostgreSQL中生成大规模随机数据的方法。这种方法&#xff0c;生成数据比较快&…

LeetCode34:在排序数组中查找元素的第一个和最后一个位置(Java)

目录 题目&#xff1a; 题解&#xff1a; 方法一&#xff1a; 方法二&#xff1a; 题目&#xff1a; 给你一个按照非递减顺序排列的整数数组 nums&#xff0c;和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。 如果数组中不存在目标值 target&…

AI大模型探索之路-应用篇11:AI大模型应用智能评估(Ragas)

目录 前言 一、为什么要做智能评估&#xff1f; 二、Ragas是什么&#xff1f; 三、Ragas使用场景 四、Ragas评估指标 五、Ragas代码实践 总结 前言 随着人工智能技术的飞速发展&#xff0c;AI大模型&#xff08;LLM&#xff09;已经成为了推动技术创新和应用的关键因素。…

数模 初见数建

文章目录 初见数学建模1.1 数学建模是什么1.2 数学建模的概述1.3 如何学习数学建模---分模块化1.4 数学建模前提了解1.5 数学建模的六个步骤1.6 如何备战建模比赛1.7 数学建模赛题类型1.8 数学建模算法体系概述 初见数学建模 1.1 数学建模是什么 1.原型与模型 原型&#xff…

Golang | Leetcode Golang题解之第28题找出字符串中第一个匹配项的下标

题目&#xff1a; 题解&#xff1a; func strStr(haystack, needle string) int {n, m : len(haystack), len(needle)if m 0 {return 0}pi : make([]int, m)for i, j : 1, 0; i < m; i {for j > 0 && needle[i] ! needle[j] {j pi[j-1]}if needle[i] needle[…

AI图书推荐:如何在课堂上使用ChatGPT 进行教育

ChatGPT是一款强大的新型人工智能&#xff0c;已向公众免费开放。现在&#xff0c;各级别的教师、教授和指导员都能利用这款革命性新技术的力量来提升教育体验。 本书提供了一个易于理解的ChatGPT解释&#xff0c;并且更重要的是&#xff0c;详述了如何在课堂上以多种不同方式…

程序猿没有副业,太难了

副业的初衷 我知道踏下心来钻研本专业会有收获,但又实在没有太多兴趣。 只好努努力,跟着兴致走,多一个选择,多一份抵抗风险的能力。 不管从事什么行业&#xff0c;除了做好自己本职工作以外&#xff0c;还会有一些空闲的时间来做一些其他事情&#xff0c;与其说是副业,不如说…

TypeScript 忽略红色波浪线

&#x1f468;&#x1f3fb;‍&#x1f4bb; 热爱摄影的程序员 &#x1f468;&#x1f3fb;‍&#x1f3a8; 喜欢编码的设计师 &#x1f9d5;&#x1f3fb; 擅长设计的剪辑师 &#x1f9d1;&#x1f3fb;‍&#x1f3eb; 一位高冷无情的全栈工程师 欢迎分享 / 收藏 / 赞 / 在看…

upload-labs第五六关

第五关 $is_upload false; $msg null; if (isset($_POST[submit])) {if (file_exists(UPLOAD_PATH)) {$deny_ext array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml"…

《手机维修600G资料》云盘下载地址

无意中发现一个生财之道&#xff0c;哈哈哈&#xff0c;就是发现有人在一些视频平台&#xff0c;发手机维修之类的视频吸引客户。这样自己就不用开店也可以接生意了。问题剩下就一个了&#xff0c;把手机维修技术学好&#xff0c;一技在手&#xff0c;天上我有。 《手机维修600…

基于java+springboot+vue实现的智能停车计费系统(文末源码+Lw+ppt)23-30

摘 要 随着人们生活水平的高速发展&#xff0c;智能停车计费信息管理方面在近年来呈直线上升&#xff0c;人们也了解到智能停车计费的实用性&#xff0c;因此智能停车计费的管理也逐年递增&#xff0c;智能停车计费信息的增加加大了在管理上的工作难度。为了能更好的维护智能…