【JaveEE】——(手把手教你)用IDEA手搓一个定时器Timer

news2024/12/23 16:05:13

阿华代码,不是逆风,就是我疯,你们的点赞收藏是我前进最大的动力!!希望本文内容能够帮助到你!

目录

一:什么是定时器

二:IDEA中的定时器Timer

1:实例化Timer

2:.schedule()方法

(1)分析

(2)具体实现

3:Timer内部前台线程

三: 自己实现定时器

1:思路

(1)阻塞队列放任务

(2)线程扫描任务

四:代码超详细解读

五:多线程安全问题

问题一:进出元素安全问题

问题二:线程饿死问题

问题三:wait和while捆绑使用

问题四:忙等

情况①:

情况②:


一:什么是定时器

前引:定时器,顾名思义,就是我们建立一个多久时间后需要执行的任务

打个比方,现在是早上8点,我告诉闹钟2个小时后提醒我该去上课了~,到10点的时候,闹钟就执行这个任务——“提醒我去上课”

代码示例:

package thread;

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

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: Hua YY
 * Date: 2024-09-26
 * Time: 8:44
 */
public class ThreadDemon32 {
    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("执行任务1,时间为1秒后");
            }
        }, 1000);

        timer.schedule(new TimerTask(){
            @Override
            public void run() {
                System.out.println("执行任务2,时间为2秒后");
            }
        },2000);


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

二:IDEA中的定时器Timer

1:实例化Timer

2:.schedule()方法

(1)分析

注意导包

(2)具体实现

3:Timer内部前台线程

从上面的运行结果不难发现,我们的main函数执行完毕了,任务也都打印出来了,但是进程还在运行,就是因为Timer内部自带有前台线程

三: 自己实现定时器

1:思路

(1)阻塞队列放任务

首先需要一个阻塞队列来放所有schedule里的任务

注:这个阻塞队列一定是一个优先级队列,通过比较器来比较,delay的大小,来安排队列顺序,执行时间小的放前面,大的放后面

PriorityQueue(线程不安全,但是可以手动加锁)

PriorityBlockingQueue(线程安全,但是不太好人为控制)

(2)线程扫描任务

然后需要一个线程来扫描任务队列,负责计时,保证到时间了,任务出队列,来执行

四:代码超详细解读

(看不懂的按照步骤自己先敲一遍)(难度很大,敲完了会爽的起飞~~~)

package thread;

import java.util.Comparator;
import java.util.PriorityQueue;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: Hua YY
 * Date: 2024-09-26
 * Time: 9:36
 */
//1:首先创建一个定时器类
class MyTimer{
    //2:需要一个线程来扫描队列(只需要指向队列中的队首元素)
    private Thread t = null;
    //3:创建一个优先级队列,思考创建完了,就得放任务进去
    //10:把任务作为泛型放进队列
    private PriorityQueue<MyTimerTask> queue = new PriorityQueue();
    //23:这里main方法中添加任务和MyTimer中出任务会引发多线程安全问题,这里我们使用锁对象来保护队列,那么在哪里加锁比较合适呢
    Object locker = new Object();
    //13:需要去写一个方法,把参数传入
    public void schedule(Runnable runnable , long delay){
        //24.5:参与到锁竞争中的schedule也需要加上锁,因为涉及到队列offer操作
        synchronized(locker){
            //14:通过MyTimerTask构造
            MyTimerTask task = new MyTimerTask(runnable , delay);
            //15:再把任务放到队列里面
            queue.offer(task);
            //26:offer后有元素了,唤醒
            locker.notify();
        }

    }

    //16:通过构造方法,让t线程扫描判断队首任务是否到达时间
    public MyTimer(){
        t = new Thread(()->{
    //17:t线程去扫描队首元素,如果时间到就删除队首元素,并执行任务,如果没到就等待
            while(true){
    //24:加锁,得加到while循环里面来,如果加到外面,当我们在main方法中newMyTimer的时候
    //就会调用构造方法加锁,然后一直while循环出不来,解不了锁,进而参与锁竞争中的schedule就解不了锁
                try{
                    synchronized(locker){
                        //17.5:如果队列为空等待
                        //27:While和notify捆绑使用,所以把if修改为while保险点,不懂的看阿华写的前面的文章哈
                        while (queue.isEmpty()){
                            //25:队列为空wait,catch异常,我们把try放到最外面
                            locker.wait();
                        }
                        //18:peek一下,获取队首元素
                        MyTimerTask task = queue.peek();
                        //19:记录下当前绝对时间
                        long curTime = System.currentTimeMillis();
                        //20:比较当前绝对时间和任务要执行的绝对时间大小
                        if(curTime >= task.getTime()){
                            //21:若大于等于,则出队列执行
                            queue.poll();
                            task.run();//此处顺序无所谓
                        }else {
                            //22:未到时间则等待
                            /*continue;*/
                            //28:省下资源
                            locker.wait(task.getTime() - System.currentTimeMillis());
                        }
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }

            }
        });
        t.start();
    }




}

//4:通过MyTimerTask这个类来描述一个任务
class MyTimerTask implements Comparable<MyTimerTask>{
    //5:任务执行时间(绝对时间,ms级别的时间戳)
    private long time;
    //6:具体要执行的任务(runnable)
    private Runnable runnable;
    //20:获取一下任务执行的绝对时间
    public long getTime(){
        return time;
    }
    //7:写构造方法对成员变量初始化
    public MyTimerTask(Runnable runnable , long delay){
    //8:time(绝对时间 = 当前时间 + 设置的倒计时)
        this.time = System.currentTimeMillis() + delay ;
        this.runnable = runnable;

    }
    //9:通过一个方法来执行任务(调用runnable中的run方法)
    public void run(){
        runnable.run();
    }

    //11:实现接口,写比较器(导java.lang这个包)
    @Override
    public int compareTo(MyTimerTask o) {
        //12:返回时间小的那个任务(多试试)——至此为第一部分
        return (int)(this.time - o.time);
    }
}
public class ThreadDemon33 {
    public static void main(String[] args) {
        MyTimer timer = new MyTimer();
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("第三次测试,等待的相对时间为三秒");
            }
        },3000);
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("第一次测试,等待的相对时间为一秒");
            }
        },1000);

        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("第二次测试,等待的相对时间为二秒");
            }
        },2000);
        System.out.println("hello main");
    }
}

 

五:多线程安全问题

问题一:进出元素安全问题

(1)添加和删除队列元素引起的线程安全问题——和while死锁

(2)解决方式

问题二:线程饿死问题

读懂上面的图之后,我们继续看哈

因为while循环执行的太快,我们刚解完锁,schedule还没拿上锁,就又被while循环里面给锁上了

所以我们引入wait,那么进而就得有notify,那么谁等待谁通知呢——队列为空wait,队列offer了就notify

问题三:wait和while捆绑使用

if改为while 

问题四:忙等

当前执行时间还没到,即进入else语句中,如果里面是continue,则会跳出语句,重新循环,此过程非常的快,非常占用cpu的资源,然而还没什么卵用,cpu这就是“瞎忙活”——简称“忙等”

我们要做的就是在等待过程中,想办法释放cpu资源

这里用sleep不合适

用wait()带有超时时间的版本 

情况①:

在wait期间,如果有新的任务添加进来,那我们的schedule就会唤醒wait,然后wait重新计算需要等待的时间

情况②:

在wait期间,没有新的任务天剑进来,那wait就会一直等待到,任务需要执行的绝对时间(这就是带有超时时间的版本的好处)自己唤醒自己

六:无注释版本全代码

package thread;

import java.util.PriorityQueue;
class MyTimer {//计时器中的线程负责扫描队列任务
    private Thread t = null;
    public Object locker = new Object();

    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线程去扫描队列,判断是否要执行任务
        t = new Thread(()->{
            while (true){
                try{
                    synchronized(locker){
                        while(queue.isEmpty()){
                            locker.wait();
                        }

                        //不为null拿到队首元素
                        MyTimerTask task = queue.peek();
                        if (System.currentTimeMillis() >= task.getTime()  ){
                            queue.poll();
                            task.run();
                        }else {
                            locker.wait(task.getTime()-System.currentTimeMillis());
                        }
                    }

                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }


        });
        t.start();
    }


}

class MyTimerTask implements Comparable<MyTimerTask>{//描述一个任务,并把这个任务扔到队列里面去
    private long time;//绝对时间
    private Runnable runnable;
    public long getTime(){
        return time;
    }
    public  MyTimerTask(Runnable runnable , long delay){
        this.time = System.currentTimeMillis() + delay;
        this.runnable = runnable;
    }
    public void run(){
        runnable.run();
    }
    @Override
    public int compareTo(MyTimerTask o) {
        return (int)(this.time - o.time);
    }
}
public class ThreadDemon33_2 {
    public static void main(String[] args) {
        MyTimer timer = new MyTimer();
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("任务一1s");
            }
        },1000);

        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("任务二2秒");
            }
        },2000);
        System.out.println("main函数");



    }

}

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

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

相关文章

C语言-动态内存分配讲解

目录 ✨1.什么是动态内存分配 &#x1f495;2.动态内存开辟函数 malloc ✨3.malloc函数的检查&#xff08;两种方法&#xff09; &#x1f495;4.动态内存释放函数 free ✨5.free 函数接收空指针 ✨6.为什么要释放动态内存 &#x1f495;7.动态内存开辟函数calloc &#…

免杀笔记 ---> 一种有想法的Indirect-Syscall

今天来分享一下&#xff0c;看到的一种Indirect-Syscall&#xff0c;也是两年前的项目了&#xff0c;但是也是能学到思路&#xff0c;从中也是能感受到杀软对抗之间的乐趣&#xff01;&#xff01;说到乐趣&#xff0c;让我想起看到过一位大佬的文章对"游褒禅山记"的…

学习RocketMQ

RocketMQ是一个分布式消息和流平台&#xff0c;它被设计为具有简单和可复制的架构&#xff0c;同时具有高性能和高可靠性。以下是RocketMQ从入门到精通的一些关键概念和示例代码。 1、安装和启动RocketMQ 1.1、下载并解压RocketMQ二进制文件。 wget https://archive.apache.…

JavaWeb--08BeanUtils:自定义转换器

在07创建了表单&#xff0c;但是获取网页信息的java代码太繁杂了&#xff0c;每次获取数据都要书写依次如下的&#xff1a; 重新创建一个web工程项目test1010---需要配置tomacat&#xff0c;具体传送门&#xff1a;CSDN 配置好了如下&#xff1a; 在里面web目录下创建一个reg…

JVM 基础知识(基础组成 )

使用场景 线上系统突然宕机,系统无法访问,甚至直接 O0M;线上系统响应速度太慢,优化系统性能过程中发现 CPU 占用过高,原因也许是因为 JVM的 GC 次数过于频繁;新项目上线,需要设置 JVM的各种参数;等 JDK / JRE / JVM JDK JDK 全称 ( Java Development Kit ) &#xff0c;是 Ja…

Git 使用方法

简介 Git常用命令 Git 全局设置 获取Git 仓库 方法二用的比较多 将仓库链接复制 在 git base here ----> git clone 仓库链接 工作区、暂存区、版本库 Git 工作区中文件中的状态 本地仓库的操作 远程仓库操作 git pull 将代码推送到远程仓库 1. git add 文件名 ---放…

C++:继承和多态,自定义封装栈,队列

1.栈&#xff1a; stack.cpp #include "stack.h"Stack::Stack():top(nullptr),len(0){} //析构函数 Stack::~Stack() {while(!empty()){pop();} }bool Stack::empty() //判断栈是否为空 {return topnullptr; }int Stack::size()//获取栈的大小 {return len; } //压…

万字长文详解Java线程知识

什么是进程、线程、协程&#xff1f; 进程 进程是计算机科学中的一个核心概念&#xff0c;它指的是在操作系统中正在执行的一个程序的实例。进程是操作系统中的一个独立执行单元&#xff0c;具有独立的内存空间和系统资源。每个进程都有自己独立的地址空间和文件描述符&#x…

循环神经网络笔记

循环神经网络学习 RNN训练方法–BPTT BPTT &#xff08;Backpropagation Through Time&#xff09;&#xff0c;这是一种用于训练循环神经网络&#xff08;RNNs&#xff09;的算法。由于 RNNs 能够处理序列数据&#xff0c;并且在每个时间步上都有内部状态&#xff0c;因此需…

南京自闭症寄宿学校:打造温馨的第二家

南京自闭症寄宿学校的愿景与广州星贝育园的温馨实践 在探讨自闭症儿童教育的广阔领域中&#xff0c;寄宿制学校以其独特的优势&#xff0c;为这些特殊的孩子提供了全方位的支持与关怀&#xff0c;致力于打造一个温馨如家的第二生活环境。虽然本文的主题是围绕南京自闭症寄宿学…

Chirp通过Sui让IoT世界变得更简单

据估计&#xff0c;未来十年内&#xff0c;联网设备的数量将增长到近400亿台。无论是追踪共享出行车辆的移动、改善食品追溯性、监控制造设施&#xff0c;还是保障家庭安全&#xff0c;物联网 ( Internet of Things&#xff0c;IoT) 对企业和消费者来说都已经成为一项关键技术。…

刷题学习日记 (1) - SWPUCTF

写这篇文章主要是想看看自己一个下午能干啥&#xff0c;不想老是浪费时间了&#xff0c;所以刷多少题我就会写多少题解&#xff0c;使用nss随机刷题&#xff0c;但是今天下午不知道为啥一刷都是SWPUCTF的。 [SWPUCTF 2021 新生赛]gift_F12 控制台ctrlf搜索flag即可&#xff0…

什么是竞争条件?

竞争条件&#xff0c;简单来说就是多个进程同时访问同一个共享资源&#xff0c;导致出现预期结果以外的错误的情况。 出现竞争条件的本质原因是cpu对程序的调度是没有特定规律的&#xff0c;某一时刻cpu处理哪个进程是不确定的。 简单写一个测试程序&#xff0c;先需要子进程和…

ubuntu安装emqx

目录 1.预先下载好emqx压缩包 2.使用tar命令解压 3.进入bin目录 5.放开访问端口18083 6.从通过ip地址访问emqx后台 7.默认用户名密码为admin/public 8.登录后台 9.资源包绑定在此博文可自取 1.预先下载好emqx压缩包 2.使用tar命令解压 sudo tar -xzvf emqx-5.0.8-el8-…

手机轻松解压 RAR 文件指南

手机通常不直接支持 RAR 文件打开&#xff0c;主要有以下几个原因。首先&#xff0c;手机操作系统的设计初衷并非为了处理各种复杂的压缩文件格式。 大多数手机内置的文件管理器主要侧重于管理手机内部存储和常见的文件类型&#xff0c;如图片、音频、视频等。对于像 RAR 这样…

【UR #1】外星人(dp思维技巧)

考虑去除后效性&#xff0c;常用方法排序状态可以直接以答案为状态来判断合法性考虑转移方向&#xff0c;向后转移&#xff0c;选与不选来定向答案 f[i][j]表示前i个数答案为j的方案数 不选i 则加上f[i][j] 的方案数 * &#xff08;n-i&#xff09;,ai可以在后面随便选。 选…

Python 课程20-Scikit-learn

前言 Scikit-learn 是 Python 中最流行的机器学习库之一&#xff0c;它提供了多种用于监督学习和无监督学习的算法。Scikit-learn 的特点是简单易用、模块化且具有高效的性能。无论是初学者还是专业开发者&#xff0c;都可以借助它进行快速原型设计和模型开发。 在本教程中&a…

为何专利对企业创新与竞争至关重要?

在当今这个技术飞速发展的时代&#xff0c;每一个创新的火花都可能成为推动行业进步的关键力量。然而&#xff0c;创新并非一蹴而就&#xff0c;它需要时间、资金与智慧的共同投入&#xff0c;更需要一套完善的保护机制来确保其成果不被轻易窃取或模仿。这一重任&#xff0c;便…

WebPage-Bootstrap框架(container类,container-fluid类,栅格系统)

1.Bootstrap Bootstrap为页面内容和栅格系统包裹了一个.container容器&#xff0c;框架预先定义类 1.1container类 响应式布局容器的宽度 手机-小于768px 宽度设置100%&#xff1b; 平板-大于等于768px 设置宽度为750px 桌面显示器-大于等于992px 设置宽度 970px 大屏幕显…

医院排班|医护人员排班系统|基于springboot医护人员排班系统设计与实现(源码+数据库+文档)

医护人员排班系统目录 目录 基于springboot医护人员排班系统设计与实现 一、前言 二、系统功能设计 三、系统实现 医护类型管理 排班类型管理 科室信息管理 医院信息管理 医护信息管理 四、数据库设计 1、实体ER图 2、具体的表设计如下所示&#xff1a; 五、核心代码…