Java 多线程(七)—— 定时器

在这里插入图片描述

定时器介绍与使用

先简单介绍一下什么是定时器:定时器类似生活中的闹钟,当时间一到,我们就会去做某些事情。
在代码层面理解就是,当我们设置的时间一到,程序就会执行我们固定的代码片段(也就是任务)

在Java 标准库中给我们提供了定时器的类 Timer

下面是构造方法:
在这里插入图片描述

Timer() 就是直接创建一个定时器,里面的属性都是默认值
Timer(boolean isDaemon) 设置定时器里的线程是否为守护线程(后台线程)
Timer(String name) 给你的定时器设置一个名字


schedule

schedule 方法是 Timer 的核心方法,这里介绍第一个schedule 方法的参数含义。
在这里插入图片描述

首先 TimerTask task 是 要执行的任务,delay 是时间设置,单位是毫秒(ms)

意思就是在多少毫秒之后就会执行哪些任务。

TimerTask 其实就是对 Runnable 的进一步封装
在这里插入图片描述
因此我们在传递task 的时候,要重写 run 方法

代码演示:

public class Demo3 {
    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        }, 5000);
    }
}

程序启动之后,5s 之后就会打印 hello

在这里插入图片描述

模拟实现

定时器需要定时执行一些任务,这些任务按照各自的时间执行,因此我们可以使用优先级队列来管理这些任务。
为了便于我们实现任务类(MyTimerTask)这里就不采用抽象类的写法,直接定义两个成员变量,一个是 Runnable 任务,一个是 delay 时间,并且在 MyTimerTask 这里实现 Comparable 接口,当然你也可以自己实现一个 Comparator

class MyTimerTask implements Comparable<MyTimerTask>{
    private Runnable task;
    private long delay;

    public MyTimerTask(Runnable task, long delay) {
        this.task = task;
        this.delay = delay;
    }

    @Override
    public int compareTo(MyTimerTask o) {
        return (int)(this.delay - o.delay);
    }

    public Runnable getTask() {
        return task;
    }

    public long getDelay() {
        return delay;
    }
}

在模拟实现定时器类中,先定义一个优先级队列。

接着我们来写schedule 方法,首先schedule 需要传入两个参数,在Java 源码里是直接传入 task 对象,这里简单一点,直接传入 Runnable ,然后再传入一个 delay 延时时间。

在这个方法中实例化我们的 MyTimerTask 对象,注意时间我们不能直接传过去,我们需要处理一下时间,把 delay + System.currentTimeMillis() 作为时间传入,System.currentTimeMillis() 是系统此时的时间,加上 delay 就是这个任务要在哪个时刻开始执行。

为了避免因为多个线程同时传入任务而导致线程安全问题,这里我们进行加锁,为什么要使用 notify ,后面会提到。

    public synchronized void schedule(Runnable task, long delay) {
        MyTimerTask timerTask = new MyTimerTask(task, System.currentTimeMillis() + delay);
        queue.offer(timerTask);
        this.notify();
    }

最后就是在定时器里创建一个线程,这里我直接在定时器实例化的时候把线程创建出来。

线程的任务就是不断从队列里获取任务然后去执行,因此当队列没有元素的时候,我们需要进行线程等待状态,什么时候结束等待呢?就是当队列添加了一个元素之后,就发出 notify 信号结束等待,所以上面的 schedule 方法最后一行代码是 this.notify() 就是为了提醒这里要结束等待。

结束等待之后,我们就要获取堆顶元素,这里先不着急 poll(),因为我们还不确定这个任务是否是现在就立刻执行,所以我们先 peek(),接着进行判断,如果 task.getDelay() <= System.currentTimeMillis() 的时候就执行任务,否则就是 task.getDelay() > System.currentTimeMillis() 那就进入线程等待,此时我们不能采取死等策略,因为这时候可能没人会发出 notify() 来唤醒这个线程,除了 schedule 方法里 有 notify 之外,可是 schedule 方法的 notify 是起到因为 队列添加了元素,队列不为空唤醒上一个 wait 的作用,因此如果这时候没有人 schedule 的话,这个线程就永远都不会被唤醒了。

因此这里采取有时间的 wait ,this.wait(task.getDelay() - System.currentTimeMillis());,可是我们知道这个 wait 是有可能因为 schedule 唤醒的,这时候后面的代码不能直接写 任务执行,万一此时队列添加了一个更早的元素的话,按道理你执行的任务就应该更新,**并且此时时间一定没到,wait 提前被唤醒,也是不能执行任务的,**所以这里我们采取 if - else 代码块来写这里的代码,当线程确实被唤醒之后,没事,再走一遍上面的流程重新获取堆顶元素即可。

    public MyTimer() {
        Thread t = new Thread(() -> {
            try {
                while(true) {
                    synchronized (this) {
                        while (queue.isEmpty()) {
                            this.wait();
                        }
                        MyTimerTask task = queue.peek();
                        if(task.getDelay() > System.currentTimeMillis()) {
                            this.wait(task.getDelay() - System.currentTimeMillis());
                        } else {
                            task.getTask().run();
                            queue.poll();
                        }
                    }
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        t.start();
    }

最终定时器模拟实现的代码:

class MyTimerTask implements Comparable<MyTimerTask>{
    private Runnable task;
    private long delay;

    public MyTimerTask(Runnable task, long delay) {
        this.task = task;
        this.delay = delay;
    }

    @Override
    public int compareTo(MyTimerTask o) {
        return (int)(this.delay - o.delay);
    }

    public Runnable getTask() {
        return task;
    }

    public long getDelay() {
        return delay;
    }
}

class MyTimer {
    private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();

    public MyTimer() {
        Thread t = new Thread(() -> {
            try {
                while(true) {
                    synchronized (this) {
                        while (queue.isEmpty()) {
                            this.wait();
                        }
                        MyTimerTask task = queue.peek();
                        if(task.getDelay() > System.currentTimeMillis()) {
                            this.wait(task.getDelay() - System.currentTimeMillis());
                        } else {
                            task.getTask().run();
                            queue.poll();
                        }
                    }
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        t.start();
    }

    public synchronized void schedule(Runnable task, long delay) {
        MyTimerTask timerTask = new MyTimerTask(task, System.currentTimeMillis() + delay);
        queue.offer(timerTask);
        this.notify();
    }
}

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

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

相关文章

Nacos-Sync-未授权进后台(建议自查)

Nacos-Sync-未授权进后台&#xff08;建议自查&#xff09; 漏洞成因 没进行权限校验。 影响范围 Nacos-Sync 3.0 发现方式 一、fofa发现 title“nacos” && title“Nacos-Sync” 二、路径拼接 /#/serviceSync 利用方式 访问之后直接是进入后台的样子~ 修复方…

【AI视频抠图整合包及教程】开启视觉分割新纪元 —— Meta SAM 2

在数字化时代&#xff0c;Meta公司推出的SAM 2&#xff08;Segment Anything Model 2&#xff09;标志着图像和视频分割技术的一个新高度。SAM 2不仅继承了前代SAM模型的卓越性能&#xff0c;更在实时处理、视频分割、交互式提示等方面实现了重大突破。以下是SAM 2的全面营销文…

【植物识别系统】Python+人工智能+深度学习+卷积神经网络算法+TensorFlow+算法模型+Django网页界面平台

一、介绍 植物识别系统&#xff0c;使用Python作为主要编程语言开发&#xff0c;通过收集常见的6中植物树叶&#xff08;‘广玉兰’, ‘杜鹃’, ‘梧桐’, ‘樟叶’, ‘芭蕉’, ‘银杏’&#xff09;图片作为数据集&#xff0c;然后使用TensorFlow搭建ResNet50算法网络模型&am…

如何通过Lua语言请求接口拿到数据

文章目录 概要http客户端通过请求下载数据 概要 当某个需求是需要在模块内请求接口拿到数据&#xff0c;需要使用http客户端调用接口 http客户端 LuaSOC请求接口官方文档 调用&#xff1a;http.request(method,url,headers,body,opts,ca_file,client_ca, client_key, clien…

【日记】感觉沟通是件很难精通的事情(1397 字)

正文 今天霜降&#xff0c;感觉得开始戴手套了&#xff0c;去年就是因为戴得早&#xff0c;所以避免了生冻疮。不知道今年还会不会有这么幸运。虽然霜降&#xff0c;但意外地天气很好呢。下午又找了个时间稍微偷溜出去发了一会儿呆。可惜每次下班天都黑了&#xff0c;天气好的话…

HCIP--1

同一区域内的OSPF路由器拥有一致的 LSDB, 在区域内&#xff0c;OSPF 采用 SPF算法计算路由一个区域太多路由器&#xff0c;硬件资源跟不上&#xff0c;所以多划分区域 OSPF 路由计算原理 1. 区域内路由计算 LSA 在OSPF中&#xff0c;每个路由器生成 LSA&#xff0c;用于告诉…

基于SpringBoot的宠物爱好者交流系统的设计与实现(源码+定制+开发)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

Facebook的AI驱动发展:人工智能如何改变社交体验

个性化内容推荐 Facebook利用AI算法分析用户的行为数据&#xff0c;包括点赞、评论、分享和浏览历史。这些数据使得平台能够深入了解用户的兴趣和偏好&#xff0c;从而提供个性化的内容推荐。例如&#xff0c;用户在浏览动态时&#xff0c;AI系统会根据用户的互动历史&#xf…

快速获取 GitHub 个人资料成就徽章

效果展示 成就徽章 个人资料中可以选择是否显示成就徽章&#xff0c;Settings-Public profile&#xff0c;勾选Show Achievements on my profile。 徽章名称获取方式Heart On Your Sleeve使用 ❤️ 表情符号对 GitHub 上的内容做出反应 (正在测试中)Open Sourcerer用户已将 P…

【芯片设计】DC综合retiming策略的学习与实践

对于DC综合中的retiming策略早有耳闻&#xff0c;但是一直没有比较系统的学习和实验过&#xff0c;正好借着这次交付过程的归纳总结机会&#xff0c;把一些零零散散的收获学习记录下。 记得刚出新手村时和某位大佬聊到过&#xff0c;他说你逻辑里写了在某级计算一个结果&#…

etl-查询错误log日志和oracle删除数据表空间

查看weblogic日志的目录 建立连接ssh root192.168.30.1xx 密码hygd123 找到下面路径中的文件 cd /home/weblogic/Oracle/Middleware/user_projects/domains/base_domain/bapp-logs 查看log日志 tail -f -n 400 Adminservers.log oracle删除表空间&#xff08;切换到dba用户…

【SoC】被忽略的reset结构设计

1024这天爆出来的设计失误&#xff0c;真的很应景啦&#xff01; 先献上A72的reset结构图吧&#xff0c;虽然最终的解决方案不是按照这个来的&#xff0c;不过也给了一个相对较清晰的reset架构了。 异步复位树 当对整个电路进行复位的时候&#xff0c;使用异步复位&#xff0c…

K8s-pod控制器HPA、DS、Job、CJ

一、Horizontal Pod Autoscaler(HPA) 在上一节&#xff0c;我们已经可以实现通过手工执行kubectl scale命令实现Pod扩容或缩容&#xff0c;但是这显然不符合Kubernetes的定位目标——自动化、智能化。 Kubernetes期望可以实现通过监测Pod的使用情况&#xff0c;实现pod数量的自…

如何通过 CRM 系统实现医药企业的一体化、数字化管理

医药企业面临着复杂的市场环境和严格的监管要求。传统管理方式下&#xff0c;销售、市场、客户服务等部门之间信息流通不畅&#xff0c;数据分散&#xff0c;导致工作效率低下&#xff0c;客户体验差。例如&#xff0c;销售团队可能不了解市场部门最新的推广活动&#xff0c;无…

大龄离异单身女性网上找陪伴搭子一起搭伙过生活的自救经历分享

在繁忙的都市生活中&#xff0c;李梅是一个独立自主的大龄离异女性。经历了婚姻的失败后&#xff0c;她更加珍惜自己的生活空间与时间&#xff0c;同时也渴望能够找到一个可以相互扶持、共度余生的伴侣。网络成为了她的新尝试领域&#xff0c;在这里&#xff0c;她希望能够遇到…

php语法学习

启动php 进入软件 打开文件&#xff1a;编写代码 $php true; $java false; var_dump($php);//输出变量细节 var_dump($java) 字符串 注意可以使用双引号也可以使用单引号 测试 $php "最好学web语言"; $java 脱胎于c语言; var_dump($php);//输出变量细节 var…

spark统一内存模型 详解

Apache Spark 是一个用于大规模数据处理的分布式计算框架&#xff0c;它支持多种处理模型&#xff08;如批处理、流处理、SQL、机器学习等&#xff09;。为了高效地在分布式环境中处理这些多样化的工作负载&#xff0c;Spark 在 2.x 版本后引入了统一内存管理模型&#xff0c;以…

配置适合Gurobi的机器硬件环境需要考虑的因素

在使用 Gurobi 进行优化计算时&#xff0c;合适的机器配置能够显著提升其求解性能&#xff0c;如何选择合适的硬件配置&#xff0c;主要从以下三个关键因素进行考虑&#xff1a; 1. CPU 主频和内存通道数 CPU 主频&#xff08;Clock Rate&#xff09; 是指处理器每秒钟能够执…

400行程序写一个实时操作系统(十三):调度器对象的创建与启动第一个任务

前言 调度器是整个RTOS的核心&#xff0c;在前面我们得到了调度器对象的框架图&#xff0c;并且简单介绍了调度器的原理。 在本节中&#xff0c;我们将会初始化调度器并且启动第一个任务。 本节内容需要一定的arm架构功底才能完全看懂&#xff0c;但是ARM架构只是RTOS这片大…

基于AI识别数据的Vue.js图像框选标注

在数字化时代&#xff0c;图像识别技术的应用越来越广泛&#xff0c;尤其是在车牌识别、人脸识别等领域。本文将介绍如何使用Vue.js框架和JavaScript创建一个交互式组件&#xff0c;该组件不仅允许用户在图片上绘制多个区域&#xff0c;加载文字&#xff0c;还提供了清空功能。…