多线程编程-定时器

news2024/10/7 4:45:51

定时器相当于一个“闹钟”,在日常生活中,我们需要闹钟的辅佐,在代码中,也经常需要“闹钟”机制(网络通信中经常需设定一个超时时间)。


一.定时器的使用

在Java标准库中,也停供了定时器的实现。

Timer类

Timer timer=new Timer();

timer类提供了一个重要的方法schedule()

需填写两个参数,用于安排任务,以及设定定时时间。

使用代码:

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);
        System.out.println("程序开始运行");
    }
}

定时器可以多个一起使用,安排任务的先后顺序取决于所设置的延迟时间

public class Demo1 {
    public static void main(String[] args) {
        Timer timer=new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello3");
            }
        },3000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello2");
            }
        },2000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello1");
            }
        },1000);
        System.out.println("程序开始运行");
    }
}

因hello1延迟时间短,所以hello1先打印,其次是hello2,最后hello3

二.定时器的实现

对于定时器来说 ,有以下几个要点:

1.创建类,描述一个要执行的任务是啥(任务的内容,任务的时间)

class MyTimerTask{
        private long time;
        private Runnable runnable;
        public MyTimerTask(Runnable runnable,long delay){
            this.runnable=runnable;
            this.time=System.currentTimeMillis()+delay;
        }
        public long getTime(){
            return time;
        }
        public void run(){
            runnable.run();
        }
}

time表示程序应该执行的时候,等于当前时间戳+延迟等待的时间。

2.管理多个任务,通过一定的数据结构,把多个任务存起来

首先,考虑使用怎样的数据结构储存。

按照我们的设想,应该是执行时间短的先执行,执行时间晚的在后面依次排队。

在学习数据结构时学到过大根堆小根堆的数据结构,与场景符合。

我们可以使用优先级队列,在队列中放入MyTimerTask类。

 private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();

但无法直接放入MyTimerTask类,需指定放入的方式(根据什么放入)

public int compareTo(MyTimerTask o) {
        return (int)(this.time-o.time);
    }

通过比较时间,将MyTimerTask类放入队列。

实现schedule方法,将任务放入队列。

  public void schedule(Runnable runnable,long delay){
                MyTimerTask myTimerTask=new MyTimerTask(runnable,delay);
                queue.offer(myTimerTask);
        }

3.有专门的线程去执行这些任务

创建线程去取出,执行队列中的任务。

首先,应该判断队列是否为空

如果为空

则需阻塞等待任务被放入队列

如果不为空,则查看任务,判断当前的时间戳有没有超过任务的时间戳。

如果没有超过,那就将任务进行阻塞等待,并设定等待的时间(任务执行的时间-当前时间戳),并在添加任务进队列时唤醒此处的等待。

注:这么做可以让出cpu资源,若是中间有执行时间更短的任务插队进来,可以让出cpu进行调度

如果当前时间>=任务执行的时间,那么就执行任务,并在执行后取出任务

class MyTimer{
    private Object lock=new Object();
        PriorityQueue<MyTimerTask> queue=new PriorityQueue<>();
        public MyTimer(){ Thread t=new Thread(()->{
            while (true) {
                synchronized (lock) {
                    while (queue.isEmpty()) {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    MyTimerTask current=queue.peek();
                    if(System.currentTimeMillis()>=current.getTime()){
                        current.run();
                        queue.poll();
                    }
                    else{
                        try {
                            lock.wait(current.getTime()-System.currentTimeMillis());
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });
            t.start();


        }

然后,再考虑线程安全,以上操作中,涉及写和执行的操作,应该加上锁运行。

完整代码如下:

import java.util.PriorityQueue;

class MyTimer{
    private Object lock=new Object();
        PriorityQueue<MyTimerTask> queue=new PriorityQueue<>();
        public MyTimer(){ Thread t=new Thread(()->{
            while (true) {
                synchronized (lock) {
                    while (queue.isEmpty()) {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    MyTimerTask current=queue.peek();
                    if(System.currentTimeMillis()>=current.getTime()){
                        current.run();
                        queue.poll();
                    }
                    else{
                        try {
                            lock.wait(current.getTime()-System.currentTimeMillis());
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });
            t.start();


        }

        public void schedule(Runnable runnable,long delay) {
            synchronized (lock) {
                MyTimerTask myTimerTask = new MyTimerTask(runnable, delay);
                queue.offer(myTimerTask);
                lock.notify();
            }
        }


}


class MyTimerTask implements Comparable<MyTimerTask>{
        private long time;
        private Runnable runnable;
        public MyTimerTask(Runnable runnable,long delay){
            this.runnable=runnable;
            this.time=System.currentTimeMillis()+delay;
        }
        public long getTime(){
            return time;
        }
        public void run(){
            runnable.run();
        }

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

测试如下:

public static void main(String[] args) {
        MyTimer myTimer=new MyTimer();
        myTimer.schedule(()->{
            System.out.println("hello 3000");
        },3000);
        myTimer.schedule(()->{
            System.out.println("hello 2000");
        },2000);
        myTimer.schedule(()->{
            System.out.println("hello 1000");
        },1000);
    }


补充

为什么队列不使用PriorityBlockingQueue而是要自己加锁?

使用阻塞队列后,queue.take()操作可能会阻塞,wait()操作也可能会阻塞,此时就有了两把锁。

两把锁,多个线程,容易出现死锁的情况。

需精心控制加锁的顺序,代码编程的复杂的提高。

如果不使用阻塞队列,可以通篇使用一把锁


以上便是全部内容,如有不对,欢迎指正

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

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

相关文章

华为OD机试 - 约瑟夫问题(Python/JS/C/C++ 2024 E卷 200分)

华为OD机试 2024E卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试真题&#xff08;Python/JS/C/C&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;私信哪吒&#xff0c;备注华为OD&#xff0c;加入华为OD刷题交流群&#xff0c;…

日语发音

这里写目录标题 一个视频教你搞懂日语音调&#xff01;【日语入门课】小白入门轻松学&#xff01;最全的日语零基础教程合集&#xff01;唱儿歌学日语&#xff5e;&#xff08;已完结&#xff09; 一个视频教你搞懂日语音调&#xff01; 中文 阴平&#xff08;第一声&#xff…

【d61】【Java】【力扣】【递归】3304. 找出第 K 个字符 I

思路 递归考虑&#xff1a;就像正常一样想出来思路&#xff0c;然后递归调用的地方&#xff0c;当作一个已经确定的量&#xff08;可直接说一个值&#xff0c;这样就不会一直向下层想&#xff09; 注意绝对不要在递归调用的地方一直往下层想&#xff0c;绝对不要&#xff0c;…

C++面试速通宝典——7

150. 数据库连接池的作用 数据库连接池的作用包括以下几个方面&#xff1a; 资源重用&#xff1a;连接池允许多个客户端共享有限的数据库连接&#xff0c;减少频繁创建和销毁连接的开销&#xff0c;从而提高资源的利用率。 统一的连接管理&#xff1a;连接池集中管理数据库连…

传感器模块编程实践(一)AS608指纹模块简介及驱动源码

文章目录 一.概要二.AS608模块主要技术指标三.AS608模块接线说明四.AS608模块通讯协议介绍五.AS608模块指纹录入与刷指纹流程六.STM32单片机与AS608模块指纹录入与刷指纹实验1.硬件准备2.软件工程3.软件主要代码4.实验效果 七.CubeMX工程源代码下载八.小结 一.概要 AS608 指纹…

打印机驱动安装教程-共享打印机修复工具-打印机扫描教程

金舟打印机驱动修复软件是驱动下载软件&#xff0c;无法解决打印机报错、打印异常、打印机无法连接等问题。 Part 1&#xff1a;打印机驱动安装教程 第一步&#xff1a;确定电脑上的打印机服务已启动 1.1右击桌面的“此电脑”然后点击“管理”。 1.2点击左侧任务栏中的“服务…

传奇GOM引擎架设好进游戏后提示请关闭非法外挂,重新登录,如何处理?

今天在架设一个GOM引擎的版本时&#xff0c;进游戏之后刚开始是弹出一个对话框&#xff0c;提示请关闭非法外挂&#xff0c;重新登录&#xff0c;我用的是绿盟登陆器&#xff0c;同时用的也是绿盟插件&#xff0c;刚开始我以为是绿盟登录器的问题&#xff0c;于是就换成原版gom…

推理攻击-Python案例

1、本文通过推理攻击的方式来估计训练集中每个类别的样本数量、某样本是否在训练集中。 2、一种简单的实现方法&#xff1a;用模型对训练数据标签进行拟合&#xff0c;拟合结果即推理为训练集中的情况。 3、了解这些案例可以帮助我们更好的保护数据隐私。 推理攻击&#xff08;…

华为最新业绩出炉!上半年营收4175亿元,同比增长34%!

华为2024年上半年经营业绩分析:稳健发展,符合预期 [中国,深圳,2024年8月29日] 今日,华为发布了其2024年上半年的经营业绩,整体表现稳健,结果符合预期。在复杂多变的全球市场环境下,华为凭借强大的创新能力和市场洞察力,实现了销售收入和净利润的显著增长。 上半年,华…

ubunut声卡配置 播放视频没有声音的解决方法 蓝牙问题

文章目录 &#x1f315;ubuntu22.04网页没有声音&#xff0c;声卡提示Dummy Output&#x1f319;方法一&#xff1a;&#xff08;亲测可行&#xff09;切换内核&#x1f319;方法二&#xff1a;&#xff08;推荐&#xff09;ubuntu22.04用pipewire替代pulseaudio⭐下载安装pipe…

Operational Concept(OpsCon)与Concept of Operations(ConOps)概念区分

Operational Concept(OpsCon)与Concept of Operations&#xff08;ConOps&#xff09;概念区分 在系统工程相关资料中会看到两个概念&#xff0c;一个是Operational Concept&#xff08;OpsCon&#xff09;&#xff0c;另一个是Concept of Operations&#xff08;ConOps&#x…

车载诊断协议DoIP系列 —— DoIP APP 应用层(AL)

车载诊断协议DoIP系列 —— DoIP APP 应用层(AL) 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师(Wechat:gongkenan2013)。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 本就是小人物,输了就是输了,不要在意别人怎么看自己。江湖一碗茶,喝…

十四、深入理解Mysql索引底层数据结构与算法

文章目录 一、索引的本质1、索引是帮助MySQL高效获取数据的排好序的数据结构2、索引的数据结构3、数据结构可视化网站 二、常见数据结构介绍1、B-Tree2、BTree&#xff08;B-Tree变种&#xff09;3、Hash结构 三、存储引擎的索引实现1、MyISAM存储引擎索引实现MyISAM索引文件和…

数理统计(第1章第2节:一些常用的抽样分布)

目录 统计量的概率分布称为“抽样分布” 1. 正态母体的子样平均数的抽样分布 正态分布 2. 卡方分布 3. t分布 4. F分布 5. 例题 6. 总结 统计量的概率分布称为“抽样分布” 1. 正态母体的子样平均数的抽样分布 正态分布 若随机变量X的概率密度为&#xff1a; 则称X服…

[C#]winform部署官方yolov11-obb旋转框检测的onnx模型

【官方框架地址】 https://github.com/ultralytics/ultralytics 【算法介绍】 Yolov11-obb&#xff08;You Only Look Once version 8 with Oriented Bounding Boxes&#xff09;是一种先进的对象检测算法&#xff0c;它在传统的Yolov3和Yolov4基础上进行了优化&#xff0c;加…

Python 如何使用 scikit-learn 进行模型训练

如何使用 scikit-learn 进行模型训练 一、简介 在现代的数据科学和机器学习领域&#xff0c;Python 已经成为最流行的编程语言之一。而其中最流行的机器学习库之一就是 scikit-learn。scikit-learn 提供了许多方便的工具和函数来实现常见的机器学习任务&#xff0c;包括数据预…

spi hal库 正点原子版

这个图 是了解一下 spi就是cs片选&#xff0c;clk时钟&#xff0c;miso主机输入从机输出&#xff0c;mosi主机输出从机输入&#xff0c;这四根线 spi最主要就是极性和相位的选择&#xff0c;spi是边沿采集&#xff0c;和iic的电平采集不一样&#xff0c;所以需要通过极性和相位…

SpringBoot 多元化配置(正则表达式,配置文件优先级)

1.配置绑定 所谓“配置绑定”就是把配置文件中的值与 JavaBean 中对应的属性进行绑定。通常&#xff0c;我们会把一些配置信息&#xff08;例如&#xff0c;数据库配置&#xff09;放在配置文件中&#xff0c;然后通过 Java 代码去读取该配置文件&#xff0c;并且把配置文件中…

【持续更新中】MMDetection3训练自己的数据集常见报错解决

博主近来跑自己数据集需要对比试验&#xff0c;故选择了MMDetection3这一算法整合详细的框架&#xff0c;遇到了较多问题在此处留作记录&#xff0c;若你也有相应的问题可以在评论区提出与解决方法。会持续更新&#xff0c;同时欢迎批评指正。 0.ModuleNotFoundError: No modu…

从博士到院士,国家级人才荣誉称号一览

在中国的科技界&#xff0c;两院院士代表着学术成就的巅峰荣誉&#xff0c;享有终身荣耀&#xff0c;并且是科研人员梦寐以求的最高职业荣誉。除了院士头衔之外&#xff0c;国家和各部委还设立了一系列针对不同年龄段学术人才的国家级荣誉称号体系&#xff0c;旨在表彰各类优秀…