多线程案例-定时器(附完整代码)

news2024/11/24 6:04:57

定时器是什么

定时器是软件开发中的一个重要组件.类似于一个"闹钟".达到一个设定的时间之后,就执行某个指定好的代码.

定时器是一种实际开发中非常常用的组件.

比如网络通信种,如果对方500ms内没有返回数据,则断开尝试重连.

比如一个Map,希望里面的某个key在3s之后过期(自动删除)

类似于这样的场景就需要用到定时器.

标准库中的定时器

标准库中提供了一个Timer类.Timer类的核心方法尾schedule.

schedule包含两个参数.第一个参数指定即将要执行的任务代码,第二个参数指定多长时间之后执行(单位为毫秒)

下面是代码示例:

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

        System.out.println("hello main");
    }
}

运行代码,我们可以观察到一上来直接打印"hello main",然后三秒之后执行了"hello timer",为什么会出现这种情况呢?显然是每个Timer内置了一个线程.

 注:由于Timer内置了线程(前台线程),会阻止进程结束.这是因为timer不知道代码是否还会添加新的任务进来,就处于一种严阵以待的状态.

所以此处需要使用cancel()方法来主动结束,否则timer不知道其它什么地方还会添加任务.

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

        System.out.println("hello main");
        Thread.sleep(4000);
        timer.cancel();
    }
}

这时就可以观察到进程结束成功了.

 

实现定时器

定时器的构成

一个带优先级队列(不要使用PriorityBlockingQueue,容易死锁!),而是使用PriorityQueue

 实现原理:

1.队列中每个元素是一个Task对象.

2.Task中带有时间属性,队首元素就是即将要执行的任务

3.同时有一个worker线程一直在扫描队首元素,看队首元素是否需要执行(先执行时间小的,后执行时间大的).

1.Timer类提供的核心接口为schedule,用于注册一个任务,并指定这个任务多长时间后执行.

public class MyTimer {
    public void schedule(Runnable runnable, long time) {
        //TODO
    }
}

2.Task类用于描述一个任务(作为Timer的内部类).里面包含着一个Runnable对象和一个time(毫秒时间戳)

这个对象需要放到优先级队列中,因此需要实现Comparable接口.

PriorityQueue, TreeMap, TreeSet都要求元素是"可比较大小的".需要Comparable,Comparator.

HashMap, HashSet则是要求元素是"可比较相等的","可hash的".因此需要实现

equals,hashCode方法

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;
    }

    @Override
    public int compareTo(MyTimerTask o) {
        //这样的写法意味着每次去除的是时间最小的元素
        //到底是谁减谁,不要记,建议随便写个试一试
        return (int)(this.time - o.time);
    }
}

3.Timer实例中,通过PriorityQueue来组织若干个Task对象.

通过schedule往队列中插入一个个Task对象

class MyTimer {
    //核心结构
    private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
    //创建一个锁对象
    private Object locker = new Object();
    
    public void schedule(Runnable runnable, long time) {
        //根据参数,构造MyTimerTask,插入队列即可
        synchronized(locker) {
            MyTimerTask myTimerTask = new MyTimerTask(runnable, delay);
            queue.offer(myTimerTask);
            locker.notify();
        }
    }
}

4.Timer类中存在一个worker线程,一直不停地扫描队首元素,看看是否能执行这个任务.

所谓能执行就是时间已经到达了. 

//在这里构建线程,负责执行具体的任务了
public MyTimer() {
    Thread t = new Thread(() -> {
        while(true) {
            try {
                synchronized(locker) {
                    //阻塞队列,只有阻塞的入队列和阻塞的出队列,没有阻塞查看队首元素
                    while(!queue.isEmpty()) {
                        locker.wait();
                    }
                    MyTimerTask myTimerTask = queue.peek();
                    long curTime = System.currentTimeMillis();
                    if(curTime >= myTimerTask.getTime()) {
                        //时间到了,可以执行任务了
                        queue.poll();
                        myTimerTask.run();
                    } else {
                        //时间还没到
                        locker.wait(myTimerTask.time - curTime);
                    }
                }
            } catch(InterruptException e) {
                e.printStackTrace();
            }
        }
    });
    t.start();
}

 下面附上完整代码以及解析:

import java.util.PriorityQueue;

//通过这个类,来描述一个任务,这一整个任务,是要放到优先级队列中的
class MyTimerTask implements Comparable<MyTimerTask>{
    //在什么时间来执行这个任务
    //此处的time是一个ms级别的时间戳
    private long time;
    //实际任务要执行的代码
    private Runnable runnable;
    //delay期望是一个相对时间
    public MyTimerTask(Runnable runnable, long delay) {
        this.runnable = runnable;
        //计算一下要执行任务的绝对时间.(使用绝对时间,方便判定任务是否到达时间的)
        this.time = System.currentTimeMillis() + delay;
    }

    //实际执行代码的方法(一般在主函数中重写)
    public void run() {
        runnable.run();
    }

    //用于获得任务执行时间
    public long getTime() {
        return this.time;
    }

    //构建一个比较器(因为在优先级队列中是按时间对任务进行比较)
    @Override
    public int compareTo(MyTimerTask o) {
        return (int)(this.time - o.time);
    }
}

public class MyTimer{
    //构建一个线程
    private Thread t = null;
    //创建存放任务的主体--优先级队列
    private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
    //创建一个锁对象
    private Object locker = new Object();

    //结束进程的方法
    public void cancel() {
        t.interrupt();
    }
    //构造方法:以用于创建线程
    public MyTimer() {
        t = new Thread(() -> {
            while(!Thread.interrupted()) {
                try {
                    synchronized (locker) {
                        while(queue.isEmpty()) {
                            //当队列为空时,这个线程就一直处于阻塞等待的状态
                            locker.wait();
                        }
                        //获得队列头部元素
                        MyTimerTask task = queue.peek();
                        //计算系统当前时间
                        long curTime = System.currentTimeMillis();
                        //判断是否应该执行
                        if(curTime >= task.getTime()) {
                            //执行任务
                            queue.poll();
                            task.run();
                        } else {
                            //等待到指定时间再执行任务
                            locker.wait(task.getTime() - curTime);
                        }
                    }
                } catch (InterruptedException e) {
                    break;
                }
            }
        });
        t.start();
    }
    //通过这个方法来执行实际的任务
    public void schedule(Runnable runnable, long delay) {
        synchronized (locker) {
            //先创建一个任务,后将任务放入队列
            MyTimerTask myTimerTask = new MyTimerTask(runnable, delay);
            queue.offer(myTimerTask);
            //唤醒因为队列中因为没有任务而阻塞等待的线程
            locker.notify();
        }
    }
}

 

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

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

相关文章

MySQL一行记录是怎么存储的?

文章目录 MySQL 一行记录是怎么存储的&#xff1f;MySQL 的数据存放在哪个文件&#xff1f;表空间文件结构 InnoDB行格式有哪些Compact行格式varchar(n) 中 n 最大取值为多少&#xff1f;行溢出后&#xff0c;MySQL是怎么处理的&#xff1f; MySQL 一行记录是怎么存储的&#x…

SpringBoot 这么实现动态数据源切换,就很丝滑!

最近在做业务需求时&#xff0c;需要从不同的数据库中获取数据然后写入到当前数据库中&#xff0c;因此涉及到切换数据源问题。本来想着使用Mybatis-plus中提供的动态数据源SpringBoot的starter&#xff1a;dynamic-datasource-spring-boot-starter来实现。 结果引入后发现由于…

【QT 5 调试软件+Linux下调用脚本shell-经验总结+初步调试+基础样例】

【QT 5 调试软件Linux下调用脚本shell-经验总结初步调试基础样例】 1、前言2、实验环境3、自我总结4、实验过程&#xff08;1&#xff09;准备工作-脚本1&#xff09;、准备工作-编写运行脚本文件2&#xff09;、给权限3&#xff09;、运行脚本 &#xff08;2&#xff09;进入q…

亚马逊测评如何安全有效地进行?完整攻略解读

亚马逊测评一直是许多亚马逊卖家快速了解市场、获得评论和提高销售的方法之一。与此同时&#xff0c;亚马逊官方对测评的控制也越来越严格。测评越来越困难&#xff0c;如果操作不当&#xff0c;可能会导致账户被禁等严重后果。 因此&#xff0c;如何安全有效地测评亚马逊已经成…

防职业掉坑必看,电商设计主要做什么?

今年双十一刚结束&#xff0c;各电商平台不公布总销售额的新闻就上了热搜。外行人乍一看可能觉得消费意愿下降&#xff0c;消费水平降级&#xff0c;电商行业不景气&#xff0c;但实际上电商领域在国内突飞猛进了10几年后&#xff0c;仍然还有很大的上升空间。很多人说&#xf…

解决高风险代码(含前后端):Insecure Randomness

Abstract 标准的伪随机数值生成器不能抵挡各种加密攻击 Explanation 在对安全性要求较高的环境中&#xff0c;使用能够生成可预测值的函数作为随机数据源&#xff0c;会产生 Insecure Randomness 错误。 电脑是一种具有确定性的机器&#xff0c;因此不可能产生真正的随机性。伪…

招不到人?用C语言采集系统批量采集简历

虽说现在大环境不太好&#xff0c;很多人面临着失业再就业风险&#xff0c;包括企业则面临着招人人&#xff0c;找对口专业难得问题。想要找到适合自己公司的人员&#xff0c;还要得通过爬虫获取筛选简历才能从茫茫人海中找到公司得力干将。废话不多说&#xff0c;直接开整。 1…

汽车底盘市场分析:预计到2025年平均渗透率达到35%

汽车底盘由传动系、行驶系、转向系和制动系四部分组成。底盘作用是支承、安装汽车发动机及其各部件、总成&#xff0c;形成汽车的整体造型&#xff0c;并接受发动机的动力&#xff0c;使汽车产生运动&#xff0c;保证正常行驶。汽车底盘容易产生故障的部位主要集中在&#xff1…

如何本地搭建WampServer并结合cpolar内网穿透实现远程访问

文章目录 前言1.WampServer下载安装2.WampServer启动3.安装cpolar内网穿透3.1 注册账号3.2 下载cpolar客户端3.3 登录cpolar web ui管理界面3.4 创建公网地址 4.固定公网地址访问 前言 Wamp 是一个 Windows系统下的 Apache PHP Mysql 集成安装环境&#xff0c;是一组常用来…

聚观早报 |小鹏P7i推出限时福利;荣耀90 GT外观曝光

【聚观365】12月13日消息 小鹏P7i推出限时福利 荣耀90 GT外观曝光 小米14 Ultra春节后登场 苹果新一代iPad Air将搭载M2芯片 Rambus发布内存控制器IP 小鹏P7i推出限时福利 小鹏汽车将旗下智驾主力车型P7i全系车型开启限时购车优惠政策&#xff0c;用户下定小鹏P7i即可享受…

医保电子凭证在项目中的集成应用

随着医保电子凭证使用普及&#xff0c;医疗行业的各个场景都要求支持医保码一码通办&#xff0c;在此分享一下&#xff0c;在C#和js中集成医保电子凭证的demo 供有需要的小伙伴参考。 一、项目效果图 在c#中集成医保电子凭证效果 在js中集成医保电子凭证效果 二、主要代码 c#…

【干货分享】KingIOServer与三菱PLC的通讯的应用案例

哈喽&#xff0c;大家好&#xff0c;我是雷工&#xff01; 最近一个项目涉及用KingIOServer采集三菱PLC数据&#xff0c;特记录通讯过程方便备忘。 一、版本说明&#xff1a; 1、KingIOServer版本&#xff1a;3.7SP2 2、PLC型号&#xff1a;Q03UDV 和Q03UDE自带以太网网口。…

阿里云国际跨境直播解决方案,视频AI创新营销模式丰富直播场景

据第三方咨询公司iiMedia Research预测&#xff0c;2017-2020年&#xff0c;视频直播行业一直处于高速发展阶段。2020年&#xff0c;视频直播行业市场收入超1万亿元&#xff0c;累计覆盖用户5.26亿。 视频直播的应用范围已从视频娱乐、电子商务等泛互联网行业扩展到在线教育、…

docker安装rabbitmq并安装死信队列插件

环境 debian11 docker 20.10.22 rabbitmq 3.10.0 拉取镜像到本地 docker pull rabbitmq3.10.0 实例化 docker run -d --name rabbit -e RABBITMQ_DEFAULT_USERadmin -e RABBITMQ_DEFAULT_PASSadmin -p 15672:15672 -p 5672:5672 -p 25672:25672 -p 61613:61613 -p 1883:…

如何利用淘宝商品详情关键词搜索电商API接口,实现个人收藏的梦想?

随着互联网的快速发展&#xff0c;电子商务市场越来越繁荣&#xff0c;个人收藏爱好者面临着越来越多的商品选择。如何能够高效地找到自己心仪的商品成为了让这一群体头疼的问题。而淘宝商品详情关键词搜索电商API接口的出现&#xff0c;联讯数据为个人实现收藏梦想提供了全新的…

ISP去噪(1)

#灵感# 因为理解的2DNR、3DNR 和当前调试平台标注的2DNR、3DNR 作用有很大差异&#xff0c;所以在网上广撒网&#xff0c;搜集知识。 目前收集出来一个这样的文章&#xff0c;有点像大学生的论文“取其精华&#xff0c;合成糟粕”。------权当一个记录册 目录 运动阈值&…

创建第一个Vue2项目-----HelloWorld

创建第一个Vue项目 第一步先去安装Vue&#xff0c;一共有两种安装方式&#xff0c;这里使用 点击这里下载&#xff1a;Vue.js 添加到自己的项目中 在使用的页面引入<script src"../js/vue.js"></script> 2. 准备好一个容器 <div id"root&qu…

迪拜义乌中国小商品城,开启中东区域展贸新商机

迪拜义乌中国小商品城&#xff0c;一颗璀璨的批发市场新星&#xff0c;正在迪拜杰贝阿里自贸区内冉冉升起。      在全球格局巨变的背景下&#xff0c;阿联酋与欧美货币深度绑定&#xff0c;同时又积极加入金砖国家集团&#xff0c;与发展中国家的经济不断融合。而作为国际…

微信小程序---wxs脚本

WXS&#xff08;WeiXin Script&#xff09;是小程序的一套脚本语言&#xff0c;结合 WXML&#xff0c;可以构建出页面的结构。 WXS 与 JavaScript 是不同的语言&#xff0c;有自己的语法&#xff0c;并不和 JavaScript 一致。 1.wxs和JavaScript的区别 2.wxs脚本基础语法 &a…