【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.mzph.cn/news/814638.shtml

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

相关文章

智过网:注册安全工程师注册有效期与周期解析

在职业领域&#xff0c;各种专业资格认证不仅是对从业者专业能力的认可&#xff0c;也是保障行业安全、规范发展的重要手段。其中&#xff0c;注册安全工程师证书在安全生产领域具有举足轻重的地位。那么&#xff0c;注册安全工程师的注册有效期是多久呢&#xff1f;又是几年一…

Elasticsearch下载安装 以及Reindex(数据迁移)

部署Elasticsearch集群 这里介绍使用的是Elasticsearch 7.6.1的版本&#xff0c;配置两台服务器&#xff0c;一台部署主节点&#xff0c;一台部署两个从节点。 下载地址&#xff1a;https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.16.2-linux-x86_64…

【Java探索之旅】方法重载 递归

&#x1f3a5; 屿小夏 &#xff1a; 个人主页 &#x1f525;个人专栏 &#xff1a; Java编程秘籍 &#x1f304; 莫道桑榆晚&#xff0c;为霞尚满天&#xff01; 文章目录 &#x1f4d1;前言一、方法重载1.1 为什么要有方法重载1.2 方法重载的概念与使用1.3 方法签名 二、递归2…

软考高级架构师:随机函数模型

一、AI 讲解 随机函数模型是理解各种随机过程和算法的一个重要概念&#xff0c;在软件工程、算法设计以及系统分析中有着广泛的应用。简而言之&#xff0c;随机函数模型是一种用于描述具有随机性的系统或过程的数学模型&#xff0c;它能够帮助我们预测和分析在不确定性下的系统…

吴恩达2022机器学习专项课程(一) 5.5 特征缩放1 5.6 特征缩放2

问题预览/关键词 什么是特征缩放&#xff1f;作用是什么&#xff1f;特征尺度和参数w权重的关系是&#xff1f;算法为什么要调节w权重&#xff1f;不进行特征缩放对梯度下降的影响&#xff1f;有特征缩放对梯度下降的影响&#xff1f;实现特征缩放的三种方法是&#xff1f;如何…

JetBrains IntelliJ IDEA 2024.1 发布 - 领先的 Java 和 Kotlin IDE

JetBrains IntelliJ IDEA 2024.1 发布 - 领先的 Java 和 Kotlin IDE 请访问原文链接&#xff1a;JetBrains IntelliJ IDEA 2024.1 (macOS, Linux, Windows) - 领先的 Java 和 Kotlin IDE&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;s…

达梦数据库导入导出工具dmfldr

达梦数据库导入导出工具dmfldr 基础信息 OS版本&#xff1a; Red Hat Enterprise Linux Server release 7.9 (Maipo) DB版本&#xff1a; DM Database Server 64 V8 DB Version: 0x7000c 03134284132-20240115-215128-200811 dmfldr工具介绍 dmfldr&#xff08;DM Fast Loade…

大厂Java笔试题之统计兔子出生问题

题目&#xff1a;有一种兔子&#xff0c;从出生后第3个月起每个月都生一只兔子&#xff0c;小兔子长到第三个月后每个月又生一只兔子。 例子&#xff1a;假设一只兔子第3个月出生&#xff0c;那么它第5个月开始会每个月生一只兔子。 一月的时候有一只兔子&#xff0c;假如兔子…

UnityShader学习计划

1.安装ShaderlabVS,vs的语法提示 2. 常规颜色是fixed 3.FrameDebugger调试查看draw的某一帧的全部信息&#xff0c;能看到变量参数的值

架构设计-权限系统之权限系统设计

系统设计权限系统 权限管控可以通俗的理解为权力限制&#xff0c;即不同的人由于拥有不同权力&#xff0c;他所看到的、能使用的可能不一样。对应到一个应用系统&#xff0c;其实就是一个用户可能拥有不同的数据权限&#xff08;看到的&#xff09;和操作权限&#xff08;使用…

前 5 名 iPhone 数据恢复软件评测

如今&#xff0c;我们似乎将整个生活都放在手机和移动设备上。他们用许多照片、备忘录、日历日期等记录了我们的生活&#xff0c;我们总是假设这些信息在我们需要时随时可以访问。但是&#xff0c;有许多情况会导致iPhone上的数据丢失&#xff0c;例如iPhone被盗&#xff0c;损…

JVM垃圾回收(GC)

目录 目录 1.GC 简介 1.1. 引言 1.2. 何为 GC 1.2.1. 手动 GC 1.2.2. 自动 GC 引用计数法 标记清除 2.GC入门分析 2.1.碎片整理 1)对象创建时&#xff0c;执行写入操作越来越耗时 2&#xff09;内存分配错误 2.2. 分代设想 2.3. 对象分配 对象内存分配过程 2.4. …

【鸿蒙开发】第二十章 Camera相机服务

1 简介 开发者通过调用Camera Kit(相机服务)提供的接口可以开发相机应用&#xff0c;应用通过访问和操作相机硬件&#xff0c;实现基础操作&#xff0c;如预览、拍照和录像&#xff1b;还可以通过接口组合完成更多操作&#xff0c;如控制闪光灯和曝光时间、对焦或调焦等。 2 …

5.8 mybatis之EnumTypeHandler详细使用

文章目录 1. 把java中枚举数据插入到数据库中2. 把数据库中值查询到java对象中 在 Java 中&#xff0c;枚举类型是一种特殊的类&#xff0c;当我们在数据库和 Java 对象之间进行映射时&#xff0c;通常需要将数据库中的某个字段&#xff08;如字符串或数字&#xff09;映射到 J…

Python基于大数据的微博的舆论情感分析,微博评论情感分析可视化系统,附源码

博主介绍&#xff1a;✌Java徐师兄、7年大厂程序员经历。全网粉丝13w、csdn博客专家、掘金/华为云等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&#x1f3fb; 不…

练习题(2024/4/13)

1长度最小的子数组 给定一个含有 n 个正整数的数组和一个正整数 target 。 找出该数组中满足其总和大于等于 target 的长度最小的 连续 子数组 [numsl, numsl1, ..., numsr-1, numsr] &#xff0c;并返回其长度。如果不存在符合条件的子数组&#xff0c;返回 0 。 示例 1&am…

Linux第89步_了解异步通知及其结构和函数

1、了解“异步通知” “异步通知”的核心就是信号。信号是采用软件模拟的“中断”&#xff0c;它由“驱动程序”主动向“应用程序”发送信号&#xff0c;并报告自己可以访问了&#xff0c;“应用程序”收到信号以后&#xff0c;就从“驱动设备”中读取或者写入数据。整个过程就…

杰发科技AC7840——CAN通信简介(3)_时间戳

0. 时间戳简介 时间戳表示的是收到该CAN消息的时刻&#xff0c;通过连续多帧的时间戳&#xff0c;可以计算出CAN消息的发送周期&#xff0c;也可以用于判断CAN消息是否被持续收到。 1. 使用步骤 注意分别是发送和接收的功能&#xff1a; 2. 现象分析_接收时间戳 看下寄存器的…

帝国cms仿《鳄鱼下载站》网站源码

仿《鳄鱼下载站》网站源码手机安卓软件网站模版 PHP网站源码 帝国cms内核 采用帝国cms7.5 环境PHPmysql 恢复数据库后如何修改密码: 双击表&#xff0c;进入对应的详细数据表&#xff0c;然后找到&#xff1a;www_96kaifa_com_enewsuser这个表&#xff0c;双击打开修改&…

mac 配置前端开发环境brew,git,nvm,nrm

我的电脑是mac 3 pro 一、配置Homebrew 打开终端&#xff0c;执行指令 /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"查看版本 brew -v 安装nvm brew install nvm 再执行 brew reinstall nvm 我这边安装好了…