多线程案例(3)

news2025/1/13 17:29:20

文章目录

  • 多线程案例三
  • 三、 定时器

大家好,我是晓星航。今天为大家带来的是 多线程案例三 相关的讲解!😀

多线程案例三

三、 定时器

定时器是什么

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

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

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

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

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

这个方法的效果是,给定时器,注册一个任务。任务不会立即执行,而是在指定时间进行执行。

标准库中的定时器

  • 标准库中提供了一个 Timer 类. Timer 类的核心方法为schedule.
  • schedule 包含两个参数. 第一个参数指定即将要执行的任务代码, 第二个参数指定多长时间之后 执行 (单位为毫秒).
System.out.println("程序启动");
//这个 Timer 类就是标准库的定时器
Timer timer = new Timer();
timer.schedule(new TimerTask() {
    @Override
    public void run() {
        System.out.println("运行定时器任务1");
    }
},1000);
timer.schedule(new TimerTask() {
    @Override
    public void run() {
        System.out.println("运行定时器任务2");
    }
},2000);
timer.schedule(new TimerTask() {
    @Override
    public void run() {
        System.out.println("运行定时器任务3");
    }
},3000);

实现定时器

定时器的构成:

  • 一个带优先级的阻塞队列

为啥要带优先级呢?

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

  • 队列中的每个元素是一个 Task 对象.
  • Task 中带有一个时间属性, 队首元素就是即将
  • 同时有一个 worker 线程一直扫描队首元素, 看队首元素是否需要执行

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

public class Timer {
    public void schedule(Runnable command, long after) {
 // TODO
   }
}

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

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

static class Task implements Comparable<Task> {
        private Runnable command;
        private long time;
        public Task(Runnable command, long time) {
            this.command = command;
            // time 中存的是绝对时间, 超过这个时间的任务就应该被执行
            this.time = System.currentTimeMillis() + time;
       }
        public void run() {
            command.run();
       }
        @Override
        public int compareTo(Task o) {
            // 谁的时间小谁排前面
            return (int)(time - o.time);
       }
   }
}

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

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

class Timer {
    // 核心结构
    private PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue();
    
    public void schedule(Runnable command, long after) {
        Task task = new Task(command, after);
        queue.offer(task);
   }    
}

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

所谓 “能执行” 指的是该任务设定的时间已经到达了.

class Timer {
 // ... 前面的代码不变
    
    public Timer() {
        // 启动 worker 线程
        Worker worker = new Worker();
        worker.start();
   }
    
    class Worker extends Thread{
        @Override
        public void run() {
            while (true) {
                try {
                    Task task = queue.take();
                    long curTime = System.currentTimeMillis();
                    if (task.time > curTime) {
                        // 时间还没到, 就把任务再塞回去
queue.put(task);
                   } else {
                        // 时间到了, 可以执行任务
task.run();
                   }
               } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
               }
           }
       }
   }
}

但是当前这个代码中存在一个严重的问题, 就是 while (true) 转的太快了, 造成了无意义的 CPU 浪费.

比如第一个任务设定的是 1 min 之后执行某个逻辑. 但是这里的 while (true) 会导致每秒钟访问队 首元素几万次. 而当前距离任务执行的时间还有很久呢.

5)引入一个 mailBox 对象, 借助该对象的 wait / notify 来解决 while (true) 的忙等问题.

class Timer {
    // 存在的意义是避免 worker 线程出现忙等的情况
    private Object mailBox = new Object(); 
}

修改 Worker 的 run 方法, 引入 wait, 等待一定的时间.

public void run() {
    while (true) {
        try {
            Task task = queue.take();
            long curTime = System.currentTimeMillis();
            if (task.time > curTime) {
                // 时间还没到, 就把任务再塞回去
                queue.put(task);
                // [引入 wait] 等待时间按照队首元素的时间来设定. 
                synchronized (mailBox) {
                    // 指定等待时间 wait
                    mailBox.wait(task.time - curTime);
               }
                
           } else {
                // 时间到了, 可以执行任务
                task.run();
           }
       } catch (InterruptedException e) {
            e.printStackTrace();
            break;
       }
   }
}

修改 Timer 的 schedule 方法, 每次有新任务到来的时候唤醒一下 worker 线程. (因为新插入的任务可能 是需要马上执行的).

public void schedule(Runnable command, long after) {
    Task task = new Task(command, after);
    queue.offer(task);
    
    // [引入 notify] 每次有新的任务来了, 都唤醒一下 worker 线程, 检测下当前是否有
    synchronized (mailBox) {
        mailBox.notify();
   }
}

完整代码

/**
* 定时器的构成:
* 一个带优先级的阻塞队列
* 队列中的每个元素是一个 Task 对象.
* Task 中带有一个时间属性, 队首元素就是即将
* 同时有一个 worker 线程一直扫描队首元素, 看队首元素是否需要执行
*/
public class Timer {
    static class Task implements Comparable<Task> {
        private Runnable command;
        private long time;
        public Task(Runnable command, long time) {
            this.command = command;
            // time 中存的是绝对时间, 超过这个时间的任务就应该被执行
            this.time = System.currentTimeMillis() + time;
       }
        public void run() {
            command.run();
       }
 @Override
        public int compareTo(Task o) {
            // 谁的时间小谁排前面
            return (int)(time - o.time);
       }
   }
    // 核心结构
    private PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue();
    // 存在的意义是避免 worker 线程出现忙等的情况
    private Object mailBox = new Object();
    class Worker extends Thread{
        @Override
        public void run() {
            while (true) {
                try {
                    Task task = queue.take();
                    long curTime = System.currentTimeMillis();
                    if (task.time > curTime) {
                        // 时间还没到, 就把任务再塞回去
queue.put(task);
                        synchronized (mailBox) {
                            // 指定等待时间 wait
mailBox.wait(task.time - curTime);
                       }
                   } else {
                        // 时间到了, 可以执行任务
task.run();
                   }
               } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
               }
           }
       }
   }
    public Timer() {
        // 启动 worker 线程
        Worker worker = new Worker();
        worker.start();
   }
    // schedule 原意为 "安排"
    public void schedule(Runnable command, long after) {
        Task task = new Task(command, after);
        queue.offer(task);
        synchronized (mailBox) {
            mailBox.notify();
       }
   }
    public static void main(String[] args) {
        Timer timer = new Timer();
        Runnable command = new Runnable() {
            @Override
            public void run() {
             System.out.println("我来了");
                timer.schedule(this, 3000);
           }
       };
        timer.schedule(command, 3000);
   }
}

我们自己写的定时器:

咱们的定时器里面,核心

1.要有一个扫描线程,负责判定时间到/执行任务

2.还要有一个数据结构,来保存所有被注册的任务

我们在当前场景下,使用优先级队列,是一个很好的选择!!!

按照时间小的,作为优先级高的,此时队首元素就是整个队列中,最先要执行的任务。此时扫描线程只需扫一下队首元素即可。不必遍历整个队列。(如果队首元素还没到执行时间,后续元素更不可能到时间!!!)

此时我们自己写的定时器基本框架就已经搭构完成,我们用MyTask这个类来创建定义要执行的任务runnable和时间戳time,而后在MyTimer中使用他们

阻塞队列,只能先把元素出队列,才好判定,不满足还得塞回去。这不像普通队列,可以直接取队首元素判定的。

定时器(自己版本)完整版:

import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.PriorityBlockingQueue;
/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: 晓星航
 * Date: 2023-07-29
 * Time: 11:20
 */
//使用这个类来表示一个定时器中的任务.
class MyTask implements Comparable<MyTask>{
    //要执行的任务内容
    private Runnable runnable;
    //任务啥时候执行,(使用毫秒时间戳表示)
    private long time;

    public MyTask(Runnable runnable, long time) {
        this.runnable = runnable;
        this.time = time;
    }
    //获取当前任务的时间
    public long getTime() {
        return time;
    }
    //执行任务
    public void run() {
        runnable.run();
    }

    @Override
    public int compareTo(MyTask o) {
        //返回小于0 大于0  0
        //this 比 o 小,返回 < o
        //this 比 o 小,返回 > o
        //this 和 o 相等,返回 = o
        //当前要实现的效果,是队首元素 是时间最小的任务
        //这俩谁减谁不要去记!!!试试就知道了。
        //要么是 this.time - o.time   要么是 o.time - this.time
        return (int)(this.time - o.time);
    }
}

//自己写的简单的定时器
class MyTimer {
    //扫描线程
    private Thread t = null;

    //有一个阻塞优先级队列,来保存任务。
    private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();

    public MyTimer() {
        t = new Thread(()->{
            while (true) {
                try {
                    //取出队首元素,检查看看队首元素任务是否到时间了。
                    //如果时间没到,就把任务塞回队列里去。
                    //如果时间到了,就把任务进行执行。
                    synchronized (this) {
                        MyTask myTask = queue.take();
                        long curTime = System.currentTimeMillis();
                        if (curTime < myTask.getTime()) {
                            //还没到点,先不必执行
                            queue.put(myTask);
                            //在 put 之后,进行一个 wait
                                this.wait(myTask.getTime() - curTime);
                        } else {
                            //时间到了!!执行任务!!
                            myTask.run();
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
    }

    //指定两个参数
    //第一个参数是 任务 内容
    //第二个参数是 任务 在多少毫秒之后执行,形如 1000
    public void schedule(Runnable runnable,long after) {
        //注意这里的时间上的换算
        MyTask task = new MyTask(runnable,System.currentTimeMillis() + after);
        queue.put(task);
        synchronized (this) {
            this.notify();
        }
    }
}
public class ThreadDemo25 {
    public static void main(String[] args) {
        MyTimer myTImer = new MyTimer();
        myTImer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("任务1");
            }
        },1000);
        myTImer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("任务2");
            }
        },2000);
        myTImer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("任务3");
            }
        },3000);
    }
}

从运行结果不难看出,我们自己写的定时器和API自带的Timer是一样的,都会按照对应的时间进行启动。

感谢各位读者的阅读,本文章有任何错误都可以在评论区发表你们的意见,我会对文章进行改正的。如果本文章对你有帮助请动一动你们敏捷的小手点一点赞,你的每一次鼓励都是作者创作的动力哦!😘

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

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

相关文章

AgileBoot - 全栈项目启动

AgileBoot-Back-End: 基于Ruoyi做了大量重构优化的基础快速开发框架。采用Springboot Vue 3 Mybatis Plus 更面向对象的业务建模 面向生产的项目。&#xff08;非玩具项目&#xff09; 首先克隆代码&#xff0c;同是克隆前端和后端的代码。 前端代码启动&#xff1a; np…

机器学习---概述(二)

文章目录 1.模型评估1.1 分类模型评估1.2 回归模型评估 2. 拟合2.1 欠拟合2.2 过拟合2.3 适当拟合总结&#xff1a; 3.深度学习3.1层次&#xff08;Layers&#xff09;&#xff1a;3.2 神经元&#xff08;Neurons&#xff09;&#xff1a;3.3 总结 1.模型评估 模型评估是机器学…

【2种方法,jmeter用一个正则提取器提取多个值!】

jmeter中&#xff0c;用json提取器&#xff0c;一次提取多个值&#xff0c;这个很多人都会。但是&#xff0c;用正则提取器一次提取多个&#xff0c;是否可以呢&#xff1f; 肯定&#xff0c;很多人都自信满满的说&#xff0c;可以&#xff01;形如&#xff1a;token":&q…

MC0111配速MC0112白日梦Ⅰ

MC0111配速 难度&#xff1a; 白银 时间限制&#xff1a;1秒 占用内存&#xff1a;128M 小码哥参加了学校的定向越野比赛&#xff0c;赛完后&#xff0c;他踌躇满志地拿着自己的成绩单&#xff0c;看着一段段的数据&#xff0c;想算一下自己整场比赛的平均配速是多少。…

无涯教程-Lua - 嵌套if语句函数

在Lua编程中&#xff0c;您可以在另一个if or else if语句中使用一个if or else if语句。 nested if statements - 语法 嵌套if 语句的语法如下- if( boolean_expression 1) then--[ Executes when the boolean expression 1 is true --]if(boolean_expression 2)then--[ Ex…

【腾讯云 Cloud Studio 实战训练营】基于Cloud Studio 通过Java实现和公众号的快速对接

目录 一、Cloud Studio是什么 1.1 Cloud Studio介绍 1.2 Cloud Studio功能特点 1.3 Cloud Studio的好处 二、实战案例 2.1 创建开发环境 2.2选择开发模板 2.3 代码编写 2.3.1 引入依赖包 2.3.2 创建Models配置类 2.3.3 创建测试类demo.java 三、使用总结 今天通过J…

数论—换元法

0x00 前言 换元法指将一个式子看做一个整体&#xff0c;进行整体运算&#xff0c;从而达到简化的目的。 0x01 例题&#xff1a; 1.求所有整数n&#xff0c;使得n1|n25 2.求所有整数n&#xff0c;使得n-2|n5n 同样使用n-2去换元即可。 3.求所有的整数n&#xff0c;使用n-1|…

c语言基础知识帮助理解(函数递归详解)

"从前有座山&#xff0c;山里有座庙&#xff0c;庙里有个老和尚和一个小和尚。有一天老和尚对小和尚说:“从前有座山.山里有座庙&#xff0c;庙里有个老和尚和一个小和尚&#xff0c;有一天老和尚对小和尚说&#xff1a;“从前有座山.山里有座庙&#xff0c;庙里有个老和尚…

uniapp返回

// 监听返回事件onNavigationBarButtonTap() {uni.showModal({title: 提示,content: 确定要返回吗&#xff1f;,success: (res) > {if (res.confirm) {uni.navigateBack({delta: 2})}}})},

Palo Alto Networks® PA-220R 下一代防火墙 确保恶劣工况下的网络安全

一、主要安全功能 1、每时每刻在各端口对全部应用进行分类 • 将 App-ID 用于工业协议和应用&#xff0c;例如 Modbus、 DNP3、IEC 60870-5-104、Siemens S7、OSIsoft PI 等。 • 不论采用何种端口、SSL/SSH 加密或者其他规避技术&#xff0c;都会识别应用。 • 使用…

opencv-34 图像平滑处理-2D 卷积 cv2.filter2D()

2D卷积是一种图像处理和计算机视觉中常用的操作&#xff0c;用于在图像上应用滤波器或卷积核&#xff0c;从而对图像进行特征提取、平滑处理或边缘检测等操作。 在2D卷积中&#xff0c;图像和卷积核都是二维的矩阵或数组。卷积操作将卷积核在图像上滑动&#xff0c;对每个局部区…

智慧消防解决方案

智慧消防解决方案引入了物联网云平台、云计算中心等创新技术&#xff0c;实现对消防物联网数据的实时采集、传输和分析处理。通过智能感知设备与云平台的紧密结合&#xff0c;该解决方案利用先进的物联网技术、移动互联网和云计算等先进技术手段&#xff0c;实现火灾报警信号的…

健身用什么耳机好、健身大神都戴的运动耳机推荐

随着健身逐渐成为一种普遍的生活状态&#xff0c;越来越多的运动者选择将音乐作为他们锻炼时的精神伴侣。一款好的运动耳机&#xff0c;佩戴的舒适性再到音质、续航、防水。大家肯定问到哪有什么运动耳机好用的呢&#xff1f;今天就为大家推荐几款合适的运动耳机 1、NANK南卡R…

差分隐私 MP-SPDZ框架安装

ubuntu虚拟机安装MP-SPDZ框架 1.下载安装包到虚拟机内 https://github.com/data61/MP-SPDZ/releases 安装git 报错Waiting for cache lock: Could not get lock /var/lib/dpkg/lock-frontend. It is held by process 4402(unattended-upgr) 解决方案 #杀死进程 sudo k…

重磅!三思LED显示闪耀第31届世界大运会五座赛事场馆

成都第31届世界大学生夏季运动会 2023中国成都。 世界大学生夏季运动会&#xff08;以下简称“大运会”&#xff09; 我们来了&#xff01; 三思LED不仅以颇具想象力的艺术手法 亮相这场高规格体育盛会 更以卓越品质为此盛大赛事保驾护航&#xff01; 揭秘|成都大运会会…

2023年电赛---运动目标控制与自动追踪系统(E题)关于网友的问题回复

前言 &#xff08;1&#xff09;各位私信问问题之前&#xff0c;看看自己的问题是不是在这个里面再问&#xff01; &#xff08;2&#xff09; <1>2023年8月3日&#xff0c;10点25分。增加第三问的细节回答。 <2>2023年8月3日&#xff0c;10点45分。更新关于舵机安…

HTML中元素和标签有什么区别?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 标签&#xff08;Tag&#xff09;⭐元素&#xff08;Element&#xff09;⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&a…

使用爬虫代理IP速度慢是什么原因?

你们有没有遇到过使用爬虫代理IP速度慢的问题呢&#xff1f;相信很多使用爬虫抓取的人都曾经陷入过这个烦恼&#xff0c;今天我们就来聊聊这个话题。 首先&#xff0c;我们得明白为什么爬虫代理IP速度会变得慢。其实&#xff0c;原因有很多&#xff0c;比如代理服务器过多的连接…

高中生台灯什么牌子好?挑选不好可能会伤眼!

台灯是现在家庭都在使用的照明灯具&#xff0c;对于晚上工作学习、看书休闲都有很大的裨益&#xff0c;尤其是正在上学阶段的孩子&#xff0c;更是必备可少的学习“伴侣”。但是在挑选台灯的过程中也要注重护眼效果&#xff0c;在此基础上才是讨论性价比的问题&#xff0c;如果…

1本Frontiers期刊不再被收录,EI期刊目录已更新 (附8月最新刊源)!

近期爱思唯尔&#xff08;Elsevier&#xff09;官网更新了EI Compendex收录期刊目录&#xff0c;这是2023年第四次更新。 Elsevier发布2023年第四版EI期刊目录 更新时间&#xff1a;2023年7月1日 不同于SCI/SSCI目录每月更新一次的频率&#xff0c;EI目录更新没有规律可言&am…