多线程(初阶八:计时器Timer)

news2024/11/25 0:47:40

目录

一、标准库中的计时器

1、计时器的概念

2、计时器的简单介绍

二、模拟实现一个计时器

1、思路

(1)计数器中要存放任务的数据结构

(2)存放优先级队列中的类型:自定义任务类MyTimerTask

(3)计数器类MyTimer

MyTimer类:

MyTimerTask任务类:

2、分析计时器的线程安全问题

(1)维护队列进出的操作

(2)当队列是空的,就要阻塞等待

(3)如果没到时间,就要等待到时在执行要执行的代码


一、标准库中的计时器

1、计时器的概念

计时器类似闹钟,有定时的功能,闹钟是到时间就会响,而计时器是到时间就会执行某一操作,可以指定时间,去执行某一任务(某一代码)。

2、计时器的简单介绍

在标准库中,提供了Timer类,Timer类的核心方法是schedule,里面包含两个参数,一个是要执行的任务代码,一个是设置多久之后执行这个任务代码的时间注意:Timer内置了线程(前台线程)

代码演示:

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

执行结果:

可以看到先打印 hello main ,等过了1s才打印 hello 1000,往后继续推,说明Timer内置了线程,main线程不用等待,而timer类是要到时间才会执行任务代码。注意:这里的线程并没有结束,可以看到idea里也没有显示线程结束,说明timer类里面内置的是前台线程。

但是timer类里面有cancel方法,可以结束线程,我们把这个方法加到打印hello 3000那方法里面,这样就可以结束timer类里面的线程了。

代码:

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

执行结果:

可以结束线程。


二、模拟实现一个计时器

1、思路

(1)计数器中要存放任务的数据结构

首先,我们知道,计时器是可以定时去执行一些任务操作,那么我们怎么每次先去执行时间小的那一操作呢?用数组吗?其实在某一些场景下确实可以用数组,但这就需要我们每次都去遍历数组,找出最小的时间,但是如果我们要定时很多任务,成千上万呢?这就不合理了,从数组里面找出这个时间最小的数据,一方面要考虑资源花销大的问题,还有要考虑时间的问题,找的时间太长,错过了已经到时要执行的任务,这说明,使用数组存放任务是不合理的。

可以用优先级队列,这样,每次拿都能拿到时间最小的任务,时间复杂度也仅仅是O(1),但是优先级队列不能是阻塞队列,不然会引起死锁问题。

(2)存放优先级队列中的类型:自定义任务类MyTimerTask

任务类是放要执行的代码和要执行任务时间,单独作为一类,存进优先级队列中,其中,优先级队列里的比较是按任务类的时间大小来比较的。

(3)计数器类MyTimer

里面有一个线程,放在MyTimer类的构造方法中,这个线程就是扫描线程,而这个扫描线程来完成判断和操作,入队列或者判断啥时候才执行要执行的代码的操作;还有创建任务schedule的方法,里面也有入队列的操作。

代码:

MyTimer类:
//通过这个类表示定时器
class MyTimer {
    Object locker = new Object();
    //负责扫描任务队列,执行任务的线程
    private Thread t = null;
    //任务队列
    private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
    //入队列的方法
    public void schedule(Runnable runnable, long delay) {
        synchronized (locker) {
            MyTimerTask task = new MyTimerTask(runnable, delay);
            queue.offer(task);
            locker.notify();
        }
    }
    //构造方法,会创建线程,让扫描线程来完成判定和执行
    public MyTimer() {
        t = new Thread(() -> {
            while (true) {
                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) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
    }
}

里面的核心代码:schedule方法,这是创建任务,里面包含了要执行的代码和执行代码的时间,还有就是构造方法,里面有一个线程,这个线程就是不断去判断队列有没有任务,到时间了的任务就拿队伍里时间最小的任务,执行这任务里的代码,没到时间就要等。

MyTimerTask任务类:

代码:

//通过这个类,来描述一个任务
class MyTimerTask implements Comparable<MyTimerTask>{
    //在什么时间点来执行任务
    private long time;//此处的time是一个ms级别的时间戳
    //实际任务要执行的代码
    private Runnable runnable;
    public MyTimerTask(Runnable runnable, long delay) {
        this.runnable = runnable;
        //计算的是要执行任务绝对时间,方便判断是否到达时间了
        this.time = System.currentTimeMillis() + delay;
    }
    //得到要执行任务时间的方法
    public long getTime() {
        return this.time;
    }
    public void run() {
        this.runnable.run();
    }
    @Override
    public int compareTo(MyTimerTask o) {
        return (int)(this.time - o.time);
    }
}

任务类里面放要是和执行的代码,和要执行代码的时间,因为要放进队列里,所以要实现一个比较器,用时间来比较,重写compareTo方法。

2、分析计时器的线程安全问题

(1)维护队列进出的操作

我们知道,不创建其他线程,就一个主线程去调用MyTimer类的话,一共就会有两个线程:主线程和 t 线程,这时候,主线程的代码是这样的

代码:

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

主线程有入队列的操作,但 t 线程也有出队列的操作,如图:

多线程操作一个队列有进有出,肯定是线程不安全的操作;所以,要维护这个队列,就要把入队列和出队列操作都上锁,同一时间要么只能入队列,要么只能出队列;

入队列操作上锁位置好知道,把创建任务和入队列操作都上锁;但是出队列呢?要在哪里上锁,把while循环都给上锁了?显然,这样的代码感觉有点危险,在这场景上确实可以用,但是,如果是在其他场景下,如果一个线程拿到锁了,但是因为没有实际来执行代码,就会不停的解锁、加锁,这样其他线程就饿死了,所以,还是在while里面,把里面的操作给上锁,这样看着没那么膈应。

如图的代码是最终版本的,上面的代码都是最终版本的。

(2)当队列是空的,就要阻塞等待

如图:

(3)如果没到时间,就要等待到时在执行要执行的代码

没到时间,就要阻塞等待,等待时间是: 要行的时间 - 现在的时间,没有限制要等待的时间的话,就会一直循环,每次循环判断是不是到时间了,因为循环这个代码执行速度是很快的,这样就会盲等,虽然计算机是在忙,都是在瞎忙活,所以代码要写出这样子,如图:


以上的代码都是计时器完整的版本,都看到这了,点个赞再走吧,谢谢谢谢谢!!!

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

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

相关文章

Vue集成MarkDown编辑器(超详细步骤)

本次博客用于来进行展示如何使用Vue来集成MarkDown编辑器&#xff0c;首先在我们的IDEA的控制台输入以下命令&#xff1a;npm install mavon-editor -S 注意&#xff0c;一定要在vue的哪个目录下执行。 接着&#xff0c;我们在main.js文件中进行引入markdown的文件。 之后我们…

华为认证 | 11月底这门HCIP认证正式发布!

华为认证openGauss高级工程师HCIP-openGauss V1.0&#xff08;中文版&#xff09;自2023年11月30日起&#xff0c;正式在中国区发布。 01 发布概述 基于“平台生态”战略&#xff0c;围绕“云-管-端”协同的新ICT技术架构&#xff0c;华为公司打造了覆盖ICT领域的认证体系&…

对数据库关系代数中除法运算的理解

一、基本概念 1.象集 给定一个关系R(X,Z)&#xff0c;X和Z为属性组&#xff0c;当t[X]x时&#xff0c;x在R中的象集定义为&#xff1a; Z x { t [ Z ] ∣ t ∈ R , t [ X ] x } Z_x\{t[Z]|t\in R,t[X]x\} Zx​{t[Z]∣t∈R,t[X]x} 表示R中属性组X上值为x的诸元组在Z上分量的…

力扣面试经典150题——Unix简化路径

https://leetcode.cn/problems/simplify-path/description/?envTypestudy-plan-v2&envIdtop-interview-150 思路&#xff1a;将串以/分割&#xff0c;判断字符串是…/./其他&#xff0c;进行入栈和出栈&#xff0c;最后留下的就是结果&#xff0c;拼装一下就好了。 三个…

SpringBoot 项目 Jar 包加密,防止反编译

1场景 最近项目要求部署到其他公司的服务器上&#xff0c;但是又不想将源码泄露出去。要求对正式环境的启动包进行安全性处理&#xff0c;防止客户直接通过反编译工具将代码反编译出来。 2方案 第一种方案使用代码混淆 采用proguard-maven-plugin插件 在单模块中此方案还算简…

【用unity实现100个游戏之18】从零开始制作一个类CSGO/CS2、CF第一人称FPS射击游戏——基础篇1(附项目源码)

文章目录 本节最终效果前言搭建环境玩家移动控制摄像机跟随和视角人物奔跑实现跳跃斜坡顿挫感人物卡墙问题源码完结 本节最终效果 前言 生存和射击游戏一直是我的最爱&#xff0c;说起3D最普遍的应该就是射击系统了&#xff0c;你可以在任何情况下加入射击功能&#xff0c;所以…

LoadRunner12.55的简介与安装

提示&#xff1a;https://mp.weixin.qq.com/s/iK-fh0VP7v8mNSDNxjkBow 文章目录 LoadRunner的简介与安装loadrunner概述loadrunner的下载与安装 LoadRunner的使用启用VuGen LoadRunner的简介与安装 LoadRunner官网&#xff1a;https://www.microfocus.com/zh-cn/products/load…

Vue学习计划-Vue2--Vue组件(一)认识组件

1.0 引入组件 传统方式编写应用 使用组件方式编写应用 1.1 模块 理解&#xff1a;向外提供特定的js程序&#xff0c;一般就是一个js文件为什么&#xff1a;js文件很多很复杂作用&#xff1a;复用js,简化js的编写&#xff0c;提高js运行效率 1.2 组件认识 理解&#xff1a; …

ElementPlus table 中嵌套 input 输入框

文章目录 需求分析 需求 vue3 项目中 使用UI组件库 ElementPlus 时&#xff0c;table 中嵌入 input输入框 分析 <template><div class"p-10"><el-table :data"tableData" border><el-table-column prop"date" label&qu…

docker镜像与容器的基本操作,容器打包以及镜像迁移

docker镜像拉取---docker pull docker pull image_name[:tag] 这是直接拉取官方镜像 image_name: 镜像的名称&#xff0c;例如 ubuntu, nginx, mysql 等。tag: 镜像的标签&#xff0c;表示版本或者特定的标识。如果未指定标签&#xff0c;默认为 latest。 例如&#xff0c;…

计算机速成课Crash Course - 08. 指令和程序

今天开始计算机速成课Crash Course的系列讲解。 更多技术文章&#xff0c;全网首发公众号 “摸鱼IT” 锁定 -上午11点 - &#xff0c;感谢大家关注、转发、点赞&#xff01; 计算机速成课Crash Course - 08. 指令和程序 08. 指令和程序 上集我们把 ALU, 控制单元, RAM, 时钟…

无需公网IP实现公网远程访问本地WebDAV服务

windows搭建WebDAV服务&#xff0c;并内网穿透公网访问【无公网IP】 文章目录 windows搭建WebDAV服务&#xff0c;并内网穿透公网访问【无公网IP】1. 安装IIS必要WebDav组件2. 客户端测试3. cpolar内网穿透3.1 打开Web-UI管理界面3.2 创建隧道3.3 查看在线隧道列表3.4 浏览器访…

威胁态势 | 0day占比超85%!两大勒索家族“均分天下”

近日&#xff0c;亚信安全正式发布《亚信安全2023年11月威胁态势报告》&#xff08;以下简称“报告”&#xff09;&#xff0c;报告显示&#xff0c;11月份新增安全漏洞778个&#xff0c; APT组织在国内外活动仍然呈上升趋势&#xff0c;多个国家政府、企业和科研单位遭受攻击&…

Linux之进程(一)

目录 一、概念 1、基本概念 2、描述进程的PCB 3、task_struct 二、查看进程 三、获取进程的PID和PPID 通过系统调用获取进程的PID和PPID 四、通过系统调用创建进程 1、fork函数创建子进程 2、用if进行分流 五、进程状态 1、操作系统进程状态 1、新建 2、运行 3、…

Java利用UDP实现简单的双人聊天

一、创建新项目 首先创建一个新的项目&#xff0c;并命名。 二、实现代码 import java.awt.*; import java.awt.event.*; import javax.swing.*; import java.net.*; import java.io.IOException; import java.lang.String;public class liaotian extends JFrame{private stat…

expdp单独导出导入dblink

文章目录 前言一、实现步骤获取ddl方式&#xff08;不可行&#xff09;expdp单独导出dblink 二、impdp单独导入dblink 前言 在实际工作中可能会遇到测试或者迁移工作&#xff0c;对于数据库建立较多的dblink应用重新建立dblink工作量较大&#xff0c;此时可以通过逻辑导出导入…

邮件协议讲解(SMTP、POP3、IMAP)

邮件收发基本概念 常见邮件协议 发邮件&#xff1a;SMTP&#xff08;加密版本SMTPS&#xff09; 收邮件&#xff1a;IMAP&#xff08;加密版本IMAPS&#xff09;、POP3&#xff08;加密版本POP3S&#xff09; 邮件协议端口号 SMTP&#xff1a; TCP 25 IMAP&#xff1a;…

西门子SMART精彩触摸屏如何在进入某个画面时置位某个BOOL变量?

西门子SMART精彩触摸屏如何在进入某个画面时置位某个BOOL变量&#xff1f; 以下举例进行说明具体的操作&#xff1a; 如下图所示&#xff0c;新建一个项目后&#xff0c;在变量表中添加好自己需要的变量&#xff1b; 添加一个画面&#xff0c;这里以“画面_1”进行举例说明&…

在 Node-RED 中引入 ECharts 实现数据可视化

Node-RED 提供了强大的可视化工具&#xff0c;而通过引入 ECharts 图表库&#xff0c;您可以更直观地呈现和分析数据。在这篇博客中&#xff0c;我们将介绍两种在 Node-RED 中实现数据可视化的方法&#xff1a;一种是引入本地 ECharts 库&#xff0c;另一种是直接使用 CDN&…

美图发布自研视觉大模型4.0,主打AI设计与AI视频【无际Ai分享资讯】

12月5-6日&#xff0c;主题为“未来AI设计”的美图创造力大会在厦门举行。美图公司发布自研AI视觉大模型MiracleVision&#xff08;奇想智能&#xff09;4.0版本&#xff0c;主打AI设计与AI视频。 AI设计方面&#xff0c;新增了矢量图形、文字特效、智能分层、智能排版四大能力…