JavaEE(系列10) -- 多线程案例3(定时器)

news2025/1/13 7:57:28

目录

1. 定时器

2. 标准库中的定时器

3. 实现定时器

3.1 创建带优先级的阻塞队列 

3.2 创建MyTask类

3.3 构建schedule方法

3.4 构建timer类中的线程

3.5 思考



1. 定时器

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

2. 标准库中的定时器

  1. 标准库中提供了一个 Timer 类. Timer 类的核心方法为 schedule .
  2. schedule 包含两个参数. 第一个参数指定即将要执行的任务代码, 第二个参数指定多长时间之后执行 (单位为毫秒).

使用步骤:

1. 实例化Timer对象

2.调用timer.schedule("任务",执行时间) 

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

        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("hello");
    }
}

 运行结果:先打印主进程的hello,后面陆续按照指定的时间进行打印每个线程的内容

3. 实现定时器

定时器的构成

  1. 一个带优先级的阻塞队列 
  2. 队列中的每个元素都是一个Task对象
  3. Task 中带有一个时间属性, 队首元素就是即将执行的任务
  4. 同时有一个 worker 线程一直扫描队首元素, 看队首元素是否需要执行

为什么使用带有优先级的堵塞队列?

答案:因为阻塞队列中的任务都有各自的执行时刻 (delay). 最先执行的任务一定是 delay 最小的. 使用带优先级的队列就可以高效的把这个 delay 最小的任务找出来.

3.1 创建带优先级的阻塞队列 

3.2 创建MyTask类

队列中的每个元素都是一个Task对象,创建Mytask类,用来描述要执行的任务,以及执行的时间.(用于传入堵塞队列)

此处需要注意:

我们需要将Mytask类实现Comparable接口,根据执行的时间进行比较. 这样才能传入带有优先级的堵塞队列.

 最终MyTask类代码为:

class MyTask implements Comparable<MyTask>{
    public Runnable runnable;
    public long time;

    public MyTask(Runnable runnable, long delay){
        // 取当前时刻的时间戳 + delay = 当前该任务实际执行的时间戳
        this.time = System.currentTimeMillis() + delay;
        this.runnable = runnable;
    }


    @Override
    public int compareTo(MyTask o) {
        //每次取出的是时间最小的元素
        return (int)(this.time -o.time);
    }
}

3.3 构建schedule方法

通过schedule方法往队列中插入Task对象

3.4 构建timer类中的线程

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

此线程的实现思路

  • 1. 线程要执行在队列中不断地取出任务  queue.take();
  • 2.取出任务要进行比较当前系统时间与任务执行时间
  • 3.如果任务执行时间小于当前系统时间,就说明要执行任务了.调用 myTask.runnable.run();
  • 4.如果当前取出的任务执行时间大于当前系统时间,就说明任务还没有到执行时间,将任务推送到队列中.同时进入堵塞等待
  •  5. 在schedule方法中,往优先级队列推送任务之后,同时加一个notify方法,用来唤醒此时正在堵塞的线程,使得堵塞等待解除,重新取队首任务进行比较时间.

 加wait notify的好处,就是work线程不需要一直进行取队首元素,这样会消耗系统资源,造成没必要的浪费,只需要等待堵塞当前距离执行任务的时间差就可以,当有新的任务添加进来的时候接触堵塞,重新进行计算时间差,再决定是否进行执行任务,还是进入堵塞状态.

public class MyTimer {
    private final PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
    // 创建一个锁对象
    private final Object locker = new Object();
    public MyTimer(){
        //1.创建一个线程
        Thread work = new Thread(()->{
           while (true){
               try {
                   synchronized (locker) {
                       //2.队列中取出一个任务
                       MyTask myTask = queue.take();
                       //3 获取当前时间
                       long curTime = System.currentTimeMillis();
                       //4. 任务执行时间与当前时间进行对比
                       if (myTask.time <=  curTime){
                           //4.1 任务执行时间小于等与当前时间,说明应该要执行任务了
                           myTask.runnable.run();
                       }else {
                           //4.1 任务执行时间大于等与当前时间,说明该任务还没有到执行的时间,再将刚才取出的任务放回原来的队列
                           queue.put(myTask);
                           locker.wait(myTask.time-curTime);

                           //针对这个wait():
                           //1.方便随时唤醒,比如当前时刻是14:00,约定14:30要执行上课任务,
                           //此时取出队首元素,发现时间没有到,就wait(任务执行时间-当前时间)
                           //2.当新的任务来了,需要比之前的队伍提前执行,那么就需要进行唤醒之前的wait(),
                           //重新取队首元素,进行比较时间,确定wait()的时间.

                       }
                   }
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
           }
        });
        work.start();
    }

    public void schedule(Runnable runnable, long delay){
        // 根据参数,构造MyTask,插入队列
        MyTask myTask = new MyTask(runnable,delay);
        queue.put(myTask);
        synchronized (locker){
            locker.notify();
        }
    }
}

3.5 思考

我们将锁加在了整个执行任务.此时我们如果只针对wait进行加锁?这样线程安全吗?不安全的话给出理由.

 

答案:会出现线程不安全的情况

比如下图解释:

        我们此时有两个线程,T1线程此时取出一个任务(执行时间为14:30),比较当前时间(14:00),还没有到执行时间,此时将任务推送给队列,但是在推送之前,此时有一个T2线程,正在插入一个新的任务(14:10),同时执行了notify操作,但是此时T1线程并没有wait,此时就空打一炮,此时T1线程开始拿到锁,进行堵塞等待,但是此时等待的时间为(14:30 - 14:00),T2线程插入的新任务还有10分钟需要执行,但是因为之前已经notify一次,此时堵塞的时间无法进行唤醒操作,所以T2线程插入的这个任务要等到14:30才能执行,这就引起线程的不安全.

 

        当我们加锁在整个(取出任务和推送任务),T1线程一定在wait之前不会使得T2线程执行notify操作,因为T1线程在加锁中,等待wait之后才会解除锁,等待T1的锁解除,T2才会执行notify操作. 进而去唤醒T1线程中的wait.

 

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

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

相关文章

chatgpt赋能Python-python_gauge

Python Gauge: 新一代的测试工具 Python Gauge是一个用于自动化测试的轻量级框架&#xff0c;它具有可扩展性和适应性&#xff0c;特别适合测试大型应用程序。Python Gauge支持多种编程语言包括Python&#xff0c;Java&#xff0c;C#和Ruby&#xff0c;因此可以应对各种情况。…

燃气管网监测系统:解析地下管道安全隐患

地下燃气管道是现代城市能源供应的重要组成部分&#xff0c;它们为居民和工业提供了安全、便利的燃气能源。然而&#xff0c;随着时间的推移&#xff0c;地下燃气管道可能出现安全隐患&#xff0c;如老化、腐蚀、机械损伤等&#xff0c;这可能导致泄漏、爆炸和环境污染等严重后…

Bits, Bytes and Integers——二进制unsigned以及Two-complement表示,十六进制

这篇文章梳理一下Bits, Bytes and Integers——二进制unsigned以及Two-complement表示&#xff0c;十六进制这些事儿。 计算机中所有数据都是用二进制的0和1组成的&#xff0c;直接上知识点。 二进制 Unsigned以及Two-complement 同样的一串二进制数&#xff0c;按照有符号…

chatgpt赋能Python-python_headq

Python heapq模块介绍 简介 heapq模块是Python语言中的一个自带优先队列模块&#xff0c;提供一个堆队列&#xff08;heap queue&#xff09;算法实现。堆是具有以下两个特点的树形数据结构&#xff1a; 父节点的键值总是小于或等于&#xff08;或总是大于或等于&#xff09…

英国VIBRO-METER VM600 CPUM 模块化CPU卡

特征 VM600 CPUM/IOCN机架控制器和通信接口卡对&#xff0c;支持Modbus RTU/TCP或PROFINET&#xff0c;以及前面板显示器使用以太网或RS-232串行连接到运行VM600 MPSx软件的计算机&#xff0c;对VM600机架中的保护卡(MPC4和AMC8)进行“一次性”配置管理前面板显示器&#xff0…

AI日报:“虚拟空间传送系统”能让你在家中七分钟环游世界

&#x1f680; “虚拟空间传送系统”能让你在家中七分钟环游世界 “虚拟空间传送系统”能让你在家中七分钟环游世界&#xff0c;由谷歌地图3D Tiles API和ChatGPT带来沉浸式旅行&#xff1b; 又有人将人生六个月交给ChatGPT进行全球旅行计划&#xff0c;但这也引发人们对于LU…

28所示范性微电子院校占地面积排名,中山大学第一!

01中山大学13725 中山大学简称中大&#xff0c;现有广州、珠海和深圳三校区五校园&#xff0c;占地面积共计13725亩。1924年&#xff0c;孙中山亲手将广州地区多所高校整合创立国立广东大学。1926年定名为国立中山大学。如今该校由1952年院系调整后分设的中山大学和中山医科大学…

爱创科技携UDI解决方案亮相CMEF盛会!

2023年5月14日-17日&#xff0c;第87届中国国际医疗器械博览会&#xff08;简称“CMEF”&#xff09;在上海圆满举行。来自全世界20余个国家和地区品牌代表&#xff0c;近5000家企业参展&#xff0c;千余位业界大咖、意见领袖共聚盛会。 CMEF被业界看作全球医疗器械产业风向标&…

Maven方式构建Spring Boot项目

文章目录 一&#xff0c;创建Maven项目二&#xff0c;添加依赖三&#xff0c;创建入口类四&#xff0c;创建控制器五&#xff0c;运行入口类六&#xff0c;访问Web页面七&#xff0c;修改访问映射路径八&#xff0c;定制启动标语1、创建标语文件2、生成标语字符串3、编辑标语文…

Golang idea panic()中报错解决

本地Golang升级到1.18后&#xff0c;发现原本写的一些代码在Goland中出现了一些红色的波浪线&#xff0c;将鼠标移到错误提示上&#xff0c;有如下的显示&#xff1a; Cannot use err (type error) as the type any 复制 image 源代码&#xff1a; conn, err : listener.Ac…

【操作系统】内存

内存的基础知识 什么是内存&#xff0c;有何作用&#xff1f; 内存可存放数据。 程序执行前需要先放到内存中才能被CPU处理——缓和CPU与硬盘之间的速度矛盾 指令的工作是基于“地址”的&#xff0c;每个地址对应一个数据的存储单元 如何把逻辑地址转换为物理地址&#xff1f…

Linux安装Redis数据库,无需公网IP实现远程连接

文章目录 1. Linux(centos8)安装redis数据库2. 配置redis数据库3. 内网穿透3.1 安装cpolar内网穿透3.2 创建隧道映射本地端口 4. 配置固定TCP端口地址4.1 保留一个固定tcp地址4.2 配置固定TCP地址4.3 使用固定的tcp地址连接 转发自cpolar内网穿透的文章&#xff1a;公网远程连接…

Linux——分析和排查系统故障

个人简介&#xff1a;云计算网络运维专业人员&#xff0c;了解运维知识&#xff0c;掌握TCP/IP协议&#xff0c;每天分享网络运维知识与技能。座右铭&#xff1a;海不辞水&#xff0c;故能成其大&#xff1b;山不辞石&#xff0c;故能成其高。 个人主页&#xff1a;小李会科技的…

分布式事务及Seata 1.6.1案例

文章目录 一、分布式事务二、什么时候需要用到分布式事务三、分布式理论CAP定理BASE理论 四、分布式事务解决方案刚性事务2PC3PC2PC和3PC对比 补偿事务TCC 基于消息队列的最终一致性本地消息表消息事务MQ事务消息和本地消息表对比 各方案常见使用场景总结 五、Seata 1.6.1测试1…

android应用市场的上线流程

国内Android应用市场有很多&#xff0c;各有各的优势&#xff0c;对于开发者而言会在每个市场发布&#xff0c;或者在主要的市场发布后其它应用市场会抓取信息并自动上线App&#xff0c;这也节省开发者上线时间。App上线前首先要申请软件著作权&#xff0c;国内应用市场上线基本…

【GPT科技系列】国内开发者调用openAI-API科技方法

1. 前言 openAI上线7个月了&#xff0c;但是随着openAI的约束越来越多&#xff0c;国内开发者想要使用openai的接口实现开发简直就是难上加难。那真的就没有办法了吗&#xff1f;no no no&#xff0c;CF解决一切不开心~ 2.准备工作 我们需要一个国际域名 注册cloudflare账号 …

OPPO哲库事件 “ 始末 ” ! 集体打哑谜?

1►OPPO哲库解散 2019 年&#xff0c;美国商务部以“科技网络安全”为由&#xff0c;将华为公司及其70家附属公司列入出口管制“实体名单”。与此同时&#xff0c;OPPO 创始人兼 CEO陈明永对外宣布&#xff0c;公司将为未来三年内投入 500 亿元用于前沿技术和深水区技术的探索…

Colab解压压缩包删除非空文件夹的方式

Colaboratory 简称“Colab”&#xff0c;Google Research 团队开发&#xff0c;任何人都可以通过浏览器编写和执行任意 Python 代码&#xff0c;尤其适合机器学习、数据分析、教育目的。Colab 是一种托管式 Jupyter 笔记本服务&#xff0c;用户无需设置&#xff0c;就可直接使用…

【Spring】Spring之publishEvent

观察者模式Spring之publishEvent事件处理 1.使用场景 这个一般什么时候使用&#xff0c;我们一般是在不同的bean直接进行信息传递&#xff0c;比如我们beanA的事件处理完后&#xff0c;需要beanB进行处理一些业务逻辑的时候这种情况就一般可以使用publish-event解决。 可用于…

如何将大批量的车辆合格证图片转为excel表格?

之前我们介绍了用金鸣识别在线将车辆合格证转为excel的操作方法&#xff0c;但这种方法有一个局限性&#xff0c;就是网页版仅支持一次性5张图片的识别转换&#xff0c;如果量大&#xff0c;我们需要分为很多次反复地进行转换&#xff0c;会略显繁琐&#xff0c;有没有一种更快…