【多线程初阶】多线程案例之定时器

news2024/11/16 23:38:33

文章目录

  • 前言
  • 1. 什么是定时器
  • 2. 标准库中的定时器
  • 3. 自己实现一个定时器
  • 总结


前言

本文主要给大家讲解多线程的一个重要案例 — 定时器.

关注收藏, 开始学习吧🧐


1. 什么是定时器

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

在这里插入图片描述

  • 定时器是一种实际开发中非常常用的组件, 前端后端开发都会使用到.
  • 比如网络通信中, 如果对方 500ms 内没有返回数据, 则断开连接尝试重连.
  • 比如一个 Map, 希望里面的某个 key 在 3s 之后过期(自动删除). 类似于这样的场景就需要用到定时器.

2. 标准库中的定时器

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

我们来使用一下标准库中的定时器.

import java.util.Timer;
import java.util.TimerTask;

public class ThreadDemo20 {
    public static void main(String[] args) {
        Timer timer = new Timer();
        // 给 timer 中注册的这个任务, 不是在调用 schedule 的线程中执行的. 
        // 而是通过 Timer 内部的线程, 来负责执行的.
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello 3");
            }
        }, 3000);

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello 2");
            }
        }, 2000);

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello 1");
            }
        }, 1000);

        System.out.println("program start");
    }
}

代码效果

请添加图片描述
可以看到, hello 1 2 3 按照时间设置依次执行. Timer 内部, 有着自己的线程. 为了保证随时可以处理新安排的任务, 这个线程会持续执行, 并且这个线程是前台线程, 不可以被打断.

接下来我们自己尝试来实现一个定时器

3. 自己实现一个定时器

要想实现一个定时器, 我们需要先想一想定时器其中的主要逻辑. 我们需要用一个类 (TimerTask) 将一个任务给描述出来, 再用一个类 (Timer) 使用数据结构将多个任务给组织起来.

  • 创建一个 TimerTask 这样的类, 用来表示一个任务. 这个任务需要包含两个方面, 任务的内容, 任务需要执行的实际时间. 我们可以使用时间戳来表示, 在 schedule 中, 先获取到当前的系统时间, 在这个基础上, 加上 delay 时间间隔, 就得到了该任务需要执行的实际时间了.
  • 创建一个 Timer 这样的类, 使用合适的数据结构, 把多个任务给组织起来. 在这里我们使用优先级队列 PriorityQueue 来实现是最合适的了, 这样队首元素永远都是第一个需要被执行的任务. 我们还需要一个扫描线程, 在获取到队首元素时间后, 根据差值来决定等待时间, 在这个时间到达之前, 不进行重复扫描, 降低扫描次数.

在实现定时器时, 我们有了以上的逻辑支撑, 还需要注意三个关键问题:

  1. 要保证线程安全, 我们需要给针对 queue 的操作进行加锁.
  2. 在扫描线程中, 不使用 sleep 来进行休眠, 而是用锁操作 wait 来代替. 因为 sleep 进入阻塞后, 不会释放锁, 这样会影响到其余进程, 如 schedule 的添加任务操作. 并且 sleep 在休眠过程中, 不方便提前中断.
  3. 我们放到优先级队列中的元素, 必须是 “可比较的”, 需要通过 Comparable 或者 Compartor 定义任务之间的比较规则. 按照需要被执行的时间顺序来比较.

掌握以上几点后, 我们便可以开始动手实现一个定时器了.

import java.util.PriorityQueue;

class MyTimerTask implements Comparable<MyTimerTask> {

    private Runnable runnable;

    private long time;

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

        this.time = System.currentTimeMillis() + delay;
    }

    public Runnable getRunnable() {
        return runnable;
    }

    public long getTime() {
        return time;
    }

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

class MyTimer {

    // 使用优先级队列, 来保存上述的 N 个任务
    private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();

    // 用来加锁的对象
    private Object locker = new Object();

    // 定时器的核心方法, 就是要把执行的任务添加到队列中.
    public void schedule(Runnable runnable, long delay) {
        synchronized (locker) {
            MyTimerTask task = new MyTimerTask(runnable, delay);
            queue.offer(task);
            // 每次来新的任务, 都唤醒一下之前的扫描线程, 让扫描线程根据新的任务重新规划时间.
            locker.notify();
        }
    }

    // MyTimer 中还需要构造一个 "扫描线程", 一方面去负责监控队首元素是否到点了, 是否应该执行; 一方面当任务到点之后,
    // 就要调用这里的 Runnable 的 Run 方法来完成任务
    public MyTimer() {
        // 扫描线程
        Thread thread = new Thread(() -> {
            while (true) {
                try {
                    synchronized (locker) {
                        while (queue.isEmpty()) {
                            // 注意, 当前如果队列为空, 此时就不应该去取这里的元素.
                            // 此处使用 wait 等待更合适.
                            // 如果使用 continue, 就会使这个线程 while 循环运行的飞快,
                            // 也会陷入一个高频占用 cpu 的状态(忙等).
                            // continue;
                            locker.wait();
                        }

                        // 拿出目前需要最早执行的任务
                        MyTimerTask task = queue.peek();
                        long curTime = System.currentTimeMillis();

                        if (curTime >= task.getTime()) {
                            // 假设当前时间是 14:01, 任务时间是 14:00,
                            // 此时就意味着应该要执行这个任务了.
                            queue.poll();
                            task.getRunnable().run();
                        } else {
                            // 让当前扫描线程休眠一下, 按照时间差来进行休眠.
                            // Thread.sleep(task.getTime() - curTime);
                            locker.wait(task.getTime() - curTime);
                        }
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        thread.start();
    }

}

总结

✨ 本文讲解了多线程案例中的一个经典案例, 定时器, 带大家简单了解了一下标准库库中的定时器, 重点需要掌握其核心的逻辑是什么, 如何自己去实现一个定时器.
✨ 想了解更多的多线程知识, 可以收藏一下本人的多线程学习专栏, 里面会持续更新本人的学习记录, 跟随我一起不断学习.
✨ 感谢你们的耐心阅读, 博主本人也是一名学生, 也还有需要很多学习的东西. 写这篇文章是以本人所学内容为基础, 日后也会不断更新自己的学习记录, 我们一起努力进步, 变得优秀, 小小菜鸟, 也能有大大梦想, 关注我, 一起学习.

再次感谢你们的阅读, 你们的鼓励是我创作的最大动力!!!!!

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

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

相关文章

Figma中文插件,让设计工作事半功倍的6大神器

Figma 凭借强大的设计功能和出色的协同体验&#xff0c;成为当前最受欢迎的 UI 设计工具之一。其插件生态为设计师提供了更多实用功能和可能性&#xff0c;大幅提高工作效率。即时设计在原型、交互、设计、协作等方面与 Figma 旗鼓相当&#xff0c;但更考虑本土设计师的实际需求…

数字孪生与GIS:优化公共交通的未来

数字孪生结合地理信息系统&#xff08;GIS&#xff09;在公共交通领域具有潜在的重大贡献&#xff0c;这种结合可以帮助城市更高效地规划、运营和改进公共交通系统。以下是一些关键方面的讨论&#xff0c;以说明数字孪生和GIS在这一领域的作用&#xff1a; 数字孪生技术的兴起…

【计算机网络】 基于TCP的简单通讯(服务端)

文章目录 流程伪代码代码实现加载库创建套接字绑定ip地址和端口号监听接受连接收发数据关闭套接字、卸载库 流程伪代码 //1、加载库——WSAStartup()//2、创建套接字——socket()//3、绑定ip和端口号——bind()//4、监听——listen()while(true){//5、接受连接——accept()whi…

kkplayer用户手册

本软件不使用任何敏感权限都可拒绝。所有资源均来自互联网&#xff0c;本软件仅供学习参考使用。 有任何问题可先尝试重装最新版 1.青少年模式 2.搜索方法 3.单个添加视频源 4.批量添加视频源 5.无法播放,无法全屏 6.DLNA投屏 7.隐私权限问题 8.数据备份和分享 9.关于广告 1. …

线程池解析

文章目录 1、平时使用哪些线程池&#xff0c;线程池默认的参数有哪些2、线程池的7个参数3、线程池状态①、线程池各个状态切换图&#xff1a; 4、线程池的使用5、线程池的好处6、线程池的整个流程7、Java的线程池说说①、线程池概念②、线程池的创建③、任务执行④、四种拒绝策…

005:根据股票代码和起始日期获取K线数据

我们改进《001》中的部分&#xff0c;因为他他没法在可视化界面输入信息&#xff0c;这样太麻烦。我们设法在可视化界面输入股票代码和起始日期&#xff0c;这样可以灵活得多。这部分&#xff0c;我们仍旧只获取日K线的数据。 import tkinter as tk from tkinter import messa…

Final Draft 12.0.9(简单好用的剧本写作工具)

Final Draft 12是一款专为编剧打造的强大写作工具&#xff0c;它将您的创意转化为精彩剧本的过程变得简单而高效。以下是推荐Final Draft 12的一些理由&#xff1a; 界面设计&#xff1a;Final Draft 12采用了简洁直观的界面设计&#xff0c;使得用户可以专注于创作&#xff0…

C语言的学习快速入门

可以按照以下步骤进行&#xff1a; 了解基本概念和语法&#xff1a;C语言是一种结构化的编程语言&#xff0c;了解基本的语法规则对于入门非常重要。可以学习关键字、变量、数据类型、运算符、控制结构等基本概念。学习编程环境&#xff1a;选择合适的编程环境&#xff0c;例如…

在linux下预览markdown的方法,转换成html和pdf

背景 markdown是一种便于编写和版本控制的格式&#xff0c;但却不便于预览——特别是包含表格等复杂内容时&#xff0c;单纯的语法高亮是远远不够的——这样就不能边预览边调整内容&#xff0c;需要找到一种预览方法。 思路 linux下有个工具&#xff0c;叫pandoc&#xff0c…

关闭手机广告的步骤

关闭手机广告的步骤 小米 1.设置→小米账号→声明与条款→系统广告→系统工具广告→关闭 2.设置→应用设置→应用管理→右上角三个点→设置→关闭“应用升级提醒”&“资源推荐” 3.桌面左滑打开负一屏→划到底部→设置→服务管理→选择关闭项目 4.桌面→打开任意文件夹…

数据库索引的分类

说到BTree首先要说一下B-Tree B-Tree(Balance Tree 多路平衡查找树)是一种平衡的多路搜索树数据结构&#xff0c;用于实现高效的查找、插入和删除操作。B树的特点是每个节点可以存储多个关键字&#xff0c;并且节点的孩子数目与关键字数目相同。通过控制节点的关键字数目和孩子…

最新影视视频微信小程序源码-带支付和采集功能/微信小程序影视源码PHP(更新)

源码简介&#xff1a; 这个影视视频微信小程序源码&#xff0c;新更新的&#xff0c;它还带支付和采集功能&#xff0c;作为微信小程序影视源码&#xff0c;它可以为用户 提供丰富的影视资源&#xff0c;包括电影、电视剧、综艺节目等。 这个小程序影视源码&#xff0c;还带有…

ue5读取自定义文件夹中内容

一、复制文件夹到Content内 二、读取文件内容&#xff0c;直接使用相对路径就可以了/Content&#xff0c;Resource Bundle存储文件夹名的变量。Load Text为自定义的读取json文件的方法&#xff0c;我之前的文章讲了怎么操作。 ue5读取外部文件_艺菲的博客-CSDN博客 三、根据js…

优优嗨聚集团:多地迎来旅游旺季,外卖市场有何变化

随着气温的升高&#xff0c;多地迎来了旅游旺季。据相关数据显示&#xff0c;今年暑期旅游市场异常火爆&#xff0c;全国旅游业收入同比增长了20%。在这样的大背景下&#xff0c;外卖市场也悄然发生了变化。 首先&#xff0c;让我们来看一下旅游市场的现状。据统计&#xff0c;…

Nginx 默认location index设置网站的默认首页

/斜杠代表location定位的路径&#xff0c;路径当中最重要的字段就是root。 root默认值就是html&#xff0c;这个就是nginx安装路径下面的html文件夹作为root的路径。默认不配置就是root下面的内容&#xff0c;index指定了主页的内容。 [rootjenkins html]# echo test > te…

Zotero同步论文、笔记

之前用 Mendeley[1]看论文&#xff0c;看中几个功能&#xff1a; tags&#xff0c;多标签分类&#xff0c;类似微信分组&#xff0c;用来快速筛&#xff08;已添加的&#xff09;某一类文献&#xff1b;同步&#xff0c;包括 pdf 和笔记&#xff08;高亮、便签、tags&#xff…

VRTE CreateLogger API log输出的问题

我查了手册&#xff0c;因为调用CreateLogger API时没有指定LogLevel&#xff0c;LogLevel设置为默认值kWarning。 如果将LogLevel声明为kInfo&#xff0c;则问题已解决&#xff1a; 顺便说一句&#xff0c;这是一种特殊的情况&#xff0c;因为当AraLOG_Remote在启动时&#xf…

国密cfca的好处

什么是国密SSL证书&#xff1f; 国密SSL证书采用我国自主研发的SM2公钥算法体系&#xff0c;支持SM2,SM3,SM4等国产密码算法及国密SSL安全协议。国密SSL证书可以满足政府机构、事业单位、大型国企、金融银行等行业客户的国产化改造和国密算法合规需求。 国密SSL证书的好处有哪…

微服务架构转型

微服务转型-架构规划 业务架构和数据架构 应用架构的规划和建设 微服务转型-服务拆分 微服务转型-和敏捷方法论集成 微服务转型-实施前技术储备 微服务转型-实施步骤

C++ 2019-2022 CSP_J 复赛试题横向维度分析(下)

本文继续讲解第4题&#xff0c;第4题是压轴题&#xff0c;难度肯定是有的。也决定了是否能够拿到一等奖的关键题&#xff0c;也是区别能力高低的筛选题。 1.2022 1.1 题目 上升点列point 1.2 题目描述 在一个二维平面内&#xff0c;给定n个整数点(xi,yi)&#xff0c;此外你…