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

JavaEE 第11节 定时器

前言

本篇博客重点介绍定时器的简单实现,帮助理解其底层原理。关于JAVA工具类自带的定时器,只会简单介绍,详细使用参阅官方文档(下文中有官方文档的连接)。

一、什么是定时器

定时器的概念非常简单。

它在软件开发中应用非常广泛。它类似于一个提前设定好的闹钟,到达指定时间后,执行设定好的任务程序。比如在网络通信中,如果在一个时间范围内没有得到响应,那么重新尝试连接网络等。

二、Java标准库中的定时器

标准库中提供了Timer类,核心方法是schedule(用于安排任务给计时器),它有两个参数:

  • TimerTask task:描述要执行的任务,实际是对Runnable的封装
  • long delay:延迟多长时间执行 ms     

如图(来自官方文档):

代码演示:

一个Timer实例只会创建一个线程(默认创建的前台线程),因此如果有多个schedule的任务,他们是串行执行的。关于Timer类的详细信息和使用请参阅oracle官方文档:Timer

如果有在多线程环境下使用计时器的需要,需要用到线程池中的ScheduledExecutorService.具体使用请参阅oracle官方文档:ScheduledExecutorService

三、设计实现一个简单的定时器

这里介绍的计时器,只包含一个线程,并且可能会出现任务阻塞(如果一个任务执行时间过长,后执行的任务可能延迟执行)

/*** 我们会使用优先级队列来判断那个任务最紧急需要先完成,所以需要实现Compareble,* 然后重写compareTo方法,用时间戳进行大小比较* 优先级队列中,存放的是一个一个要执行的任务*/
class MyTimerTask implements Comparable<MyTimerTask> {//用绝对时间——时间戳来表示时间间隔long time;//要执行的任务Runnable runnable;//构造方法public MyTimerTask(Runnable runnable, long delay) {//执行时间等于当前时间+延迟时间大小this.time = System.currentTimeMillis() + delay;this.runnable = runnable;}//用于执行任务public void run() {runnable.run();}//获取需要执行的任务的执行时间,等一下有用public long getTime() {return time;}@Overridepublic int compareTo(MyTimerTask o) {//创建一个小根堆,时间到的先执行return (int) (this.time - o.time);}
}class MyTimer {//把schedule的任务放到这里,然后去除优先级大的任务,判断时间,是否需要执行PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();//创建一个锁对象private Object locker = new Object();public void schedule(Runnable runnable, long delay) {//涉及优先级队列的读写操作,需要加锁保证线程安全synchronized (locker) {//把runnable和delay封装到myTimerTask中MyTimerTask myTimerTask = new MyTimerTask(runnable, delay);//放入优先级队列queue.offer(myTimerTask);//通知唤醒myTimer创建的线程locker.notify();}}//构造方法,一旦创建这个实例,开始在优先级队列里面循环获取方法执行(如果到达要执行的时机)public MyTimer() {Thread t = new Thread(() -> {while (true) {try {//下面涉及对优先级队列的读写操作,需要保证线程安全synchronized (locker) {//目前没有任务,就一直睡,直到schedule了新的任务while (queue.isEmpty()) {locker.wait();}//有了任务,先看看当前需不需要执行MyTimerTask task=queue.peek();//时间已经到了,或者超时了,就执行if(System.currentTimeMillis()>=task.time){task.run();//一处执行完的任务queue.poll();}else{//没到时间,就睡到任务执行的时候locker.wait(task.time-System.currentTimeMillis());}}} catch (InterruptedException e) {e.printStackTrace();}}});//启动线程t.start();}
}public class Test {public static void main(String[] args) throws InterruptedException {MyTimer myTimer = new MyTimer();myTimer.schedule(() -> {System.out.println("hello 1000");}, 1000);myTimer.schedule(() -> {System.out.println("hello 2000");}, 2000);myTimer.schedule(() -> {System.out.println("hello 3000");}, 3000);System.out.println("程序开始运行");}
}

执行结果:

此程序需要注意的几个点:

1)不要把下面的while换成if语句:

2)不能使用Thread.slee()替换wait()

原因有2

1、Thread.sleep在睡眠的时候是不会释放锁的(抱着锁睡),也就是睡眠的时候,schedule操作是无法进行的,会造成死锁

2、notify和wait本来就是搭配起来用,实现线程之间通信的。

如果在myTimer线程睡眠的时候,有新的任务进来,notify会重新唤醒线程,而Thread.sleep是没有这个功能的。


http://www.mrgr.cn/news/2011.html

相关文章:

  • 【MySQL】数据的基本操作(CRUD)
  • 基于深度学习的结合物理定律的预测模型
  • In-sensor zoom功能调试笔记
  • Java 类和对象
  • 【4.0】vue3中的属性
  • https握手过程详解
  • linux常用基础命令
  • Unity | AmplifyShaderEditor插件基础(第二集:模版说明)
  • 代码随想录算法训练营第三十一天| 01背包问题 二维 01背包问题 一维 416. 分割等和子集
  • HTTP/2:网络传输的革新与优化
  • Unity开发抖音小游戏广告部分接入
  • Linux索引节点不足引起Mysql报can not create to file /tmp/xxx Errcode:28的解决方案
  • Apollo9.0 PNC源码学习之Planning模块—— Lattice规划(三):静态障碍物与动态障碍物ST图构建
  • ArcGIS Pro SDK (十二)布局 5 布局图片元素
  • Linux Bridge VLAN
  • HTML浏览器缓存(Browser Cache)
  • sm2和md5前端对密码加密的方法
  • 2024华为OD机试真题- 贪吃的猴子Python-C卷D卷-200分
  • JS获取当前设备名称
  • WPF 动画 插值动画、关键帧动画、路径动画