【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() {
            @Override
            public void run() {
                System.out.println("Hello 3000");
            }
        }, 3000);

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("Hello 2000");
            }
        }, 2000);

        timer.schedule(new TimerTask() {
            @Override
            public 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;
    }

    @Override
    public 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() {
            @Override
            public void run() {
                System.out.println("hello 3000");
            }
        }, 3000);

        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello 2000");
            }
        }, 2000);

        myTimer.schedule(new Runnable() {
            @Override
            public 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.newScheduledThreadPool
        ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(1);

        threadPool.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello 3000");
            }
        }, 3000, TimeUnit.MILLISECONDS);

        threadPool.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello 2000");
            }
        }, 2000, TimeUnit.MILLISECONDS);

        threadPool.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello 1000");
            }
        }, 1000, TimeUnit.MILLISECONDS);

        threadPool.shutdown();
    }
}

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

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

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

python基础——类型注解【变量,函数,Union】

&#x1f4dd;前言&#xff1a; 上一篇文章Python基础——面相对象的三大特征提到&#xff0c;python中的多态&#xff0c;python中&#xff0c;类型是动态的&#xff0c;这意味着我们不需要在声明变量时指定其类型。然而&#xff0c;这可能导致运行时错误&#xff0c;因为我们…

HTML5学习记录

简介 超文本标记语言&#xff08;HyperText Markup Language&#xff0c;简称HTML&#xff09;&#xff0c;是一种用于创建网页的标准标记语言。 编辑器 下载传送门https://code.visualstudio.com/ 下载编辑器插件 标题 标题通过 <h1> - <h6> 标签进行定义。 …

前端开发攻略---Vue实现防篡改水印的效果。删除元素无效!更改元素属性无效!支持图片、元素、视频等等。

1、演示 2、水印的目的 版权保护&#xff1a;水印可以在图片、文档或视频中嵌入作者、品牌或版权所有者的信息&#xff0c;以防止未经授权的复制、传播或使用。当其他人使用带有水印的内容时&#xff0c;可以追溯到原始作者或版权所有者&#xff0c;从而加强版权保护。 身份识…

2024mathorcup妈妈杯C题数学建模无水印高质量论文新鲜出炉

以下展示部分正文内容&#xff1a;完整内容见文末名片 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 添加图片…

搭建个人智能家居 4 -WS2812B-RGB灯

搭建个人智能家居 4 - WS2812B-RGB灯 前言说明ESPHomeHomeAssistant 前言 上一篇文章我们已经完成了第一个外设的添加&#xff08;一个LED灯&#xff09;&#xff0c;今天接着来“壮大”这个系统&#xff0c;添加第二个外设“RGB灯”。 环境搭建可以回顾前面的文章。前文回顾&…

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

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

版本控制工具Git的使用

1、Git的基本概念和使用 1、Git是什么? ● Git: 是一个开源的分布式版本控制系统&#xff0c;可以有效、高速的处理从很小到非常大的项目版本管理。 ● GitHub: 全球最大的面向开源及私有软件项目的托管平台,免费注册并且可以免费托管开源代码。 ● GitLab:与GitHub类似&a…

2024认证杯数学建模C题思路模型代码

目录 2024认证杯数学建模C题思路模型代码&#xff1a;4.11开赛后第一时间更新&#xff0c;获取见文末名片 以下为2023年认证杯C题&#xff1a; 2024年认证杯数学建模C题思路模型代码见此 2024认证杯数学建模C题思路模型代码&#xff1a;4.11开赛后第一时间更新&#xff0c;获…

(云HIS)云医院管理系统源码 SaaS模式 B/S架构 基于云计算技术

一、云HIS系统框架简介 1、技术框架 &#xff08;1&#xff09;总体框架&#xff1a; SaaS应用&#xff0c;全浏览器访问 前后端分离&#xff0c;多服务协同 服务可拆分&#xff0c;功能易扩展 &#xff08;2&#xff09;技术细节&#xff1a; 前端&#xff1a;AngularNg…

【C++】深度解析--拷贝构造函数(从0开始,详解浅拷贝到深拷贝,小白一看就懂!!!)

目录 一、前言 二、拷贝构造函数 &#x1f34e;概念解析 &#x1f95d;特性解析 &#x1f4a6;为什么拷贝构造函数使用传值方式会引发无穷递归调用&#xff1f; &#x1f4a6;为什么拷贝构造函数的形参中要加入 const 修饰 &#x1f4a6;若未显式定义&#xff0c;编译器会生…

使用阿里云试用Elasticsearch学习:4. 聚合——2

近似聚合 如果所有的数据都在一台机器上&#xff0c;那么生活会容易许多。 CS201 课上教的经典算法就足够应付这些问题。如果所有的数据都在一台机器上&#xff0c;那么也就不需要像 Elasticsearch 这样的分布式软件了。不过一旦我们开始分布式存储数据&#xff0c;就需要小心…

干货 | 百亿节点,毫秒级延迟,基于nebula的大规模图应用实践

背景 2017年9月携程金融成立&#xff0c;在金融和风控业务中&#xff0c;有多种场景需要对图关系网络进行分析和实时查询&#xff0c;传统关系型数据库难以保证此类场景下的关联性能&#xff0c;且实现复杂性高&#xff0c;离线关联耗时过长&#xff0c;因此对图数据库的需求日…

人工智能科普:人工智能的分类

人工智能的分类多种多样&#xff0c;根据不同的标准和应用场景&#xff0c;可以将其划分为多个不同的类别。以下是对人工智能分类的详细探讨。 一、按应用领域分类 1. 智能机器人&#xff1a;智能机器人是人工智能技术在机器人领域的应用。它们能够根据环境和任务的不同进行自…

视觉SLAM学习打卡【10】-后端·滑动窗口法位姿图

本节是对上一节BA的进一步简化&#xff0c;旨在提高优化实时性.难点在于位姿图部分的雅可比矩阵求解&#xff08;涉及李代数扰动模型求导&#xff09;&#xff0c;书中的相关推导存在跳步&#xff08;可能数学功底强的人认为过渡的理所当然&#xff09;&#xff0c;笔者参考了知…

ELK企业级日志分析系统以及多种部署

目录 ELK简介 ELK简介 ELK平台是一套完整的日志集中处理解决方案&#xff0c;将 ElasticSearch、Logstash 和 Kiabana 三个开源工具配合使用&#xff0c; 完成更强大的用户对日志的查询、排序、统计需求。 ●ElasticSearch&#xff1a;是基于Lucene&#xff08;一个全文检索引…

Command开源AI的未来

在AI的浩瀚宇宙中&#xff0c;有一个新星正在闪耀——Command R。这个开源的大型语言模型不仅在技术排行榜上名列前茅&#xff0c;更以其开放性和高性能赢得了全球开发者的关注和喜爱。 开源精神的胜利 Command R是由CohereAI推出的一款开源大语言模型&#xff0c;拥有1040亿…

CTFshow电子取证——内存取证2

接上回 JiaJia-CP-2 2.佳佳在网页上登录了自己的邮箱&#xff0c;请问佳佳的邮箱是&#xff1f; 因为是在网页上登陆的邮箱 用iehistory插件 查看一下网页历史记录 为了方便分析&#xff0c;使用grep命令正则匹配一下 **com 的记录 vol.py -f JiaJia_Co.raw --profileWin…

线程池-异步编排-完成时回调-线程串行化

上图中用exceptionally可以感知异常也可以处理返回结果 同时 我们使用handle也可以做到这种情况 线程串行化

IDEA Warnings:SQL dialect is not configured.

springboot项目XxxMapper.xml文件打开后显示warnings&#xff1a;SQL dialect is not configured......&#xff08;翻译&#xff1a;未配置SQL语言。&#xff09; 大概意思是没有在IDEA中配置当前sql是MySQl、Oracle还是MariaDB等语言。 配置一下就好&#xff1a; 完了&#…