Java定时器

目录

什么是定时器?

如何使用定时器?

schedule

Timer的构造方法

cancel

定时器的模拟实现

思路分析

实现过程

完整代码


什么是定时器?

定时器:即在设定的时间时执行某事的设备(例如闹钟,在指定的时间响铃),Java中的定时器会在到达设定的时间后,执行指定的代码

Java标准库中提供了一个定时器 Timer类 供我们使用,位于java.util中

如何使用定时器?

schedule

对于Timer类,其核心方法为schedule

public void schedule(TimerTask task, long delay)

其中包含两个参数,

TimerTask是一个抽象类,其子类是一个可以被Timer执行的任务,要执行的任务代码在run()方法中实现

 task 即到达时间后要执行的任务代码,其必须是 TimerTask 的子类,通过继承TimerTask类并重写run()方法来指定具体的任务

delay即指定要等待的时间(单位为毫秒)

Timer timer = new Timer();
timer.schedule(new TimerTask() {
    @Override
    public void run() {
        System.out.println("3000");
    }
},3000);//3秒后打印3000
public void schedule(TimerTask task, Date time)

 在time时间执行task任务一次

public void schedule(TimerTask task, long delay, long period) 

在delay后执行task一次,之后每period时间后执行task

public void schedule(TimerTask task, Date firstTime, long period)

 在firstTime时执行task一次,之后每period时间后执行task,若时间为过去时间,则会立即执行

Timer的构造方法

Timer timer = new Timer()

public Timer() {
    this("Timer-" + serialNumber());
}

调用this("Timer-" + serialNumber()),以 Timer- +序列号作为定时器的名字

Timer timer = new Timer(String name) 以name作为定时器的名字

Timer timer = new Timer(boolean isDeamon) 是否将该定时器作为守护线程执行

Timer timer = new Timer(String name, boolean isDeamon) 以name作为定时器名字,是否将该定时器作为守护线程

cancel

cancel方法用于终止Timer线程

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

//定时器的使用
public class Demo {
    public static void main(String[] args) throws InterruptedException {
        Timer timer = new Timer();
        Date date = new Date(System.currentTimeMillis());
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        },date,2000);
        Thread.sleep(5000);//若主线程不休眠,直接执行cancel方法,则定时器还来不及执行就被关闭了
        timer.cancel();
    }
}

注:

1. 每一个Timer仅对应一个线程,而不是每调用一次schedule就创建一个线程

2. Timer是线程安全的

定时器的模拟实现

了解了什么是定时器和定时器的使用之后,那么定时器是如何实现的呢?

我们通过模拟实现定时器来进一步了解定时器的原理

这里我们仅模拟 Timer 不带参数的构造方法 和 等待delay时间后执行task的schedule

思路分析

要想实现定时器,首先我们要分析定时器需要完成的功能,以及如何实现这些功能

Timer类通过schedule添加等待delay时间后执行的任务代码,因此我们需要一个容器来存放这些任务,且先到达指定时间的代码先执行,因此我们可以使用优先级队列来存放 task(其中task带有时间属性,记录任务执行的时间),队首元素是最先执行的任务

同时,我们也需要有一个线程来扫描队首元素,判断队首元素是否需要执行

因此,模拟定时器需完成:

1. 优先级队列,用于存放task,队首元素是最先执行的任务

2. task中带有时间属性,记录task执行的时间

3. 线程worker 扫描队首元素,判断队首元素是否需要执行

4. 保证定时器线程安全

实现过程

我们首先创建MyTimer:

class MyTimer{
    private Thread worker = null;//用来扫描队首元素,判断其是否需要执行
    //任务队列,优先执行时间短的任务线程
    private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
    public void schedule(Runnable runnable, long delay){
      
    }
}

MyTimer中包含一个优先级队列,其中存放任务task,因此我们创建MyTimer类:

MyTimerTask类用于描述一个任务(作为Timer的内部类),里面包含一个Runnable对象和一个time(毫秒时间戳)(由于传入的delay是等待时间,因此要将其转换为执行时间)

class MyTimerTask{
    private long time;//ms级别的时间戳
    private Runnable runnable;//要执行的代码
    //构造方法
    public MyTimerTask(Runnable runnable, long delay){
        this.runnable = runnable;
        //计算要执行的时间
        this.time = System.currentTimeMillis() + delay;
    }

    //run方法
    public void run(){
        runnable.run();
    }

    public long getTime(){
        return  time;//返回时间戳
    }
}

由于MyTimerTask对象要放到优先级队列中,因此必须可比较这里我们实现Comparable接口,使其可以进行比较

重写其中的compareTo方法,让执行时间小的元素优先出队列

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

接下来我们实现schedule方法,

schedule方法要实现的功能为:将新增的任务添加到队列中

//通过schedule,添加要执行的线程
public void schedule(Runnable runnable, long delay){
    MyTimerTask task = new MyTimerTask(runnable, delay);
    //将任务添加到队列中
    queue.offer(task);
}

然后我们来实现MyTimerTask的构造方法:

在构造方法中,我们要实现的功能有:

1. 扫描队首元素,判断其是否到达执行时间,若到达执行时间,就执行任务代码,若未到达时间,则等待

由于我们要保证线程安全,因此我们需要相关操作进行加锁

在这里,我们通过创建锁对象进行加锁(也可以通过this进行加锁)

我们实现实现worker扫描队首元素

worker要反复扫描队首元素,然后判断队首元素是否到达指定时间

public MyTimer(){
    worker = new Thread(()-> {
        //反复扫描队首元素,然后判定队首元素是否到时间
        //未到时间,等待
        //到时间,执行任务并将其从任务队列中删除
        while (true) {
            if (queue.isEmpty()) {
                //队列为空,要等待添加任务

            }
            //队列不为空,获取队首元素
            MyTimerTask task = queue.peek();
            //获取当前时间
            long curTime = System.currentTimeMillis();
            //判断是否到任务时间
            if (curTime >= task.getTime()) {
                task.run();
                queue.poll();
            } else {
                //未到任务执行时间,等待

            }
        }
    });
    worker.start();
}

在判断队列为空时,要等待调用schedule方法向队列中添加元素后才解除阻塞状态,因此我们可以使用wait()方法,等待schedule唤醒,然后也可能由于其他原因被意外唤醒,因此我们使用while循环来判断队列是否为空,在结束阻塞状态后,再进行一次判断,保证队列不为空

而在未到达任务时间时,则使用 wait(task.getTime() - curTime) 等待指定时间后再解除阻塞状态,然后再进行判断。而若是在等待期间插入了新的任务,也需要解除阻塞状态,判断新插入的是否需要先执行、是否到达执行时间

因此,在schedule方法中,完成添加任务操作后,需要唤醒阻塞的线程

而在执行添加任务操作时,也需要进行加锁,保证线程安全

    public void schedule(Runnable runnable, long delay){
        synchronized (locker) {
            MyTimerTask task = new MyTimerTask(runnable, delay);//将任务添加到队列中
            queue.offer(task);
            //将任务添加到队列中后,就可以唤醒阻塞的扫描线程了
            locker.notify();
        }
    }

接下来,我们对构造方法中的判断和执行操作进行加锁

       public MyTimer(){
        worker = new Thread(()->{
            //扫描线程反复扫描队首元素,然后判定队首元素是否到时间
            //未到时间,阻塞
            //到时间,执行任务并将其从任务队列中删除
            while (true) {
                try{
                    synchronized (locker) {
                        while (queue.isEmpty()) {
                            //阻塞等待
                            locker.wait();
                        }
                        MyTimerTask task = queue.peek();
                        //获取当前时间
                        long curTime = System.currentTimeMillis();
                        //判断是否到任务时间
                        if (curTime >= task.getTime()) {
                            task.run();
                            queue.poll();
                        } else {
                            //阻塞等待
                            locker.wait(task.getTime() - curTime);

                        }
                    }
                }catch (InterruptedException e){
                    e.printStackTrace();
                }

            }
        });
        worker.start();
    }

完整代码

MyTimerTask:

class MyTimerTask implements Comparable<MyTimerTask>{
    private long time;//ms级别的时间戳
    private Runnable runnable;//要执行的代码
    //构造方法
    public MyTimerTask(Runnable runnable, long delay){
        this.runnable = runnable;
        //计算要执行的绝对时间
        this.time = System.currentTimeMillis() + delay;
    }

    //run方法
    public void run(){
        runnable.run();
    }

    public long getTime(){
        return  time;//返回时间戳
    }

    //重写compareTo,通过时间进行比较

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

MyTimer:

//模拟实现定时器
class MyTimer{
    private Thread worker = null;//用来扫描队首元素,判断其是否需要执行
    //任务队列,优先执行时间短的任务线程
    private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
    //创建锁对象
    private Object locker = new Object();

    //通过schedule,添加要执行的线程
    public void schedule(Runnable runnable, long delay){
        synchronized (locker) {
            MyTimerTask task = new MyTimerTask(runnable, delay);//将任务添加到队列中
            queue.offer(task);
            //将任务添加到队列中后,就可以唤醒阻塞的扫描线程了
            locker.notify();
        }
    }

    //构造方法,创建扫描线程,让扫描线程进行判定和执行
        public MyTimer(){
        worker = new Thread(()->{
            //扫描线程反复扫描队首元素,然后判定队首元素是否到时间
            //未到时间,阻塞
            //到时间,执行任务并将其从任务队列中删除
            while (true) {
                try{
                    synchronized (locker) {
                        while (queue.isEmpty()) {
                            //阻塞等待
                            locker.wait();
                        }
                        MyTimerTask task = queue.peek();
                        //获取当前时间
                        long curTime = System.currentTimeMillis();
                        //判断是否到任务时间
                        if (curTime >= task.getTime()) {
                            task.run();
                            queue.poll();
                        } else {
                            //阻塞等待
                            locker.wait(task.getTime() - curTime);

                        }
                    }
                }catch (InterruptedException e){
                    e.printStackTrace();
                }

            }
        });
        worker.start();
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

python期刊稿件在线投稿系统q2ud0

本系统的用户可分为管理员、投稿者、审稿人和编辑四个用户角色组成。管理员可以管理系统内所有功能&#xff0c;主要有个人中心、投稿者管理、审稿人管理、编辑管理、个人稿件管理、审核稿件管理、稿件信息管理、类型管理等功能&#xff1b;编辑登录系统主要有个人中心、审核稿…

大模型训练过程概述

上图来自 Andrej Karpathy&#xff0c;深度学习的大拿&#xff0c;目前在Tesla。这张图的信息量相当大&#xff0c;通过该图能让我们对大模型的整个训练过程有一个总体的了解。 从该图可知大模型训练主要有4步&#xff1a; Pretraining — 预训练阶段 Supervised Finetuning&…

透过许战海矩阵洞察安记食品增长战略

引言&#xff1a;安记食品如果想实施增长战略&#xff0c;建议深耕招牌产品,走向全国市场,目前招牌产品咖哩和复合调味粉市场空间没有被全面释放出来,需要科学的产品战略作为支撑。安记食品选择功能性产品方向是正确的,但“功能性”需要一个大品类作为载体,牛奶,饮料是最大的载…

C# Onnx yolov8n forklift detection

目录 效果 模型 项目 代码 下载 C# Onnx yolov8n forklift detection 效果 模型 Model Properties ------------------------- date&#xff1a;2023-12-25T16:22:05.530078 author&#xff1a;Ultralytics task&#xff1a;detect license&#xff1a;AGPL-3.0 https:/…

牛客网SQL训练4—SQL进阶挑战

文章目录 一、增删改操作1. 插入记录2. 更新记录3. 删除记录 二、表与索引操作1. 表的创建、修改与删除2. 索引的创建、删除 三、聚合分组查询1. 聚合函数2. 分组查询 四、多表查询1. 嵌套子查询2. 合并查询3. 连接查询 五、窗口函数1. 专用窗口函数2. 聚合窗口函数 六、其他常…

分布式【4. 什么是 CAP?】

什么是 CAP&#xff1f; C 代表 Consistency&#xff0c;一致性&#xff0c;是指所有节点在同一时刻的数据是相同的&#xff0c;即更新操作执行结束并响应用户完成后&#xff0c;所有节点存储的数据会保持相同。 A 代表 Availability&#xff0c;可用性&#xff0c;是指系统提…

开源radishes高仿网易云音乐完整源码,可试听和下载“灰色”歌曲,跨平台的无版权音乐平台

源码介绍 Radishes是项目名称&#xff0c;是由萝卜翻译而来。可以在这里试听和下载“灰色”歌曲&#xff0c;是一个可以跨平台的无版权音乐平台。 萝卜音乐界面和功能参考 windows 网易云音乐界面和 ios 的网易云音乐 安装依赖 cd radishes/ yarn bootstrap 运行项目 web:…

【回溯】0-1背包Python实现

文章目录 [toc]问题描述形式化描述 回溯法时间复杂性Python实现 个人主页&#xff1a;丷从心 系列专栏&#xff1a;回溯法 问题描述 给定 n n n种物品和一背包&#xff0c;物品 i i i的重量是 w i w_{i} wi​&#xff0c;其价值为 v i v_{i} vi​&#xff0c;背包的容量为 c …

力扣每日一题99:恢复二叉搜索树

题目 给你二叉搜索树的根节点 root &#xff0c;该树中的 恰好 两个节点的值被错误地交换。请在不改变其结构的情况下&#xff0c;恢复这棵树 。 示例 1&#xff1a; 输入&#xff1a;root [1,3,null,null,2] 输出&#xff1a;[3,1,null,null,2] 解释&#xff1a;3 不能是 1 …

vue保姆级教程----组件之间的参数传递

&#x1f4e2; 鸿蒙专栏&#xff1a;想学鸿蒙的&#xff0c;冲 &#x1f4e2; C语言专栏&#xff1a;想学C语言的&#xff0c;冲 &#x1f4e2; VUE专栏&#xff1a;想学VUE的&#xff0c;冲这里 &#x1f4e2; CSS专栏&#xff1a;想学CSS的&#xff0c;冲这里 &#x1f4…

QT 输入框输入限制 正则表达式限制 整理

在使用 输入数值时&#xff0c;经常遇到限制其范围的需要&#xff0c;比如角太阳高度角范围为[-90,90]&#xff0c;经度值范围[-180,180]&#xff0c;方位角范围[0,360]。Qt提供了QIntValidator和QDoubleValidator可以限定数值输入范围&#xff0c;如使用QIntValidator限制整数…

RK3568测试tdd

RK3568测试tdd 一、门禁取包二、烧录三、跑tdd用例四、查看结果参考资料 一、门禁取包 右键复制链接&#xff0c;粘贴下载&#xff1b;解压到文件夹&#xff1b; 二、烧录 双击\windows\RKDevTool.exe打开烧写工具&#xff0c;工具界面击烧写步骤如图所示&#xff1a; 推荐…

单片机的存储、堆栈与程序执行方式

一、单片机存储区域 如图所示位STM32F103ZET6的参数&#xff1a; 单片机的ROM&#xff08;内部FLASH&#xff09;&#xff1a;512KB&#xff0c;用来存放程序代码的空间。 单片机的RAM&#xff1a;64KB&#xff0c;一般都被分配为堆、栈、变量等的空间。 二、堆和栈的概念 …

中间人攻击是什么,会产生哪些危害,如何有效防止中间人攻击

简介 中间人攻击&#xff08;Man-in-the-Middle Attack&#xff0c;简称MITM攻击&#xff09;是一种网络攻击&#xff0c;其原理是攻击者通过各种技术手段将受攻击者控制的一台计算机虚拟放置在网络连接中的两台通信计算机之间&#xff0c;这台计算机称为“中间人”。在攻击过…

关于HTTPS

目录 什么是加密 对称加密 非对称加密 中间人攻击 引入证书 HTTPS是一个应用层的协议,是在HTTP协议的基础上引入了一个加密层. HTTP协议内容都是按照文本的方式明文传输,这就导致在传输的过程中出现一些被篡改的情况. 运营商劫持事件 未被劫持的效果,点击下载按钮,就会…

Spring Cloud Gateway 常见过滤器的基本使用

目录 1. 过滤器的作用 2. Spring Cloud Gateway 过滤器的类型 2.1 内置过滤器 2.1.1 AddResponseHeader 2.1.2 AddRequestHeader 2.1.3 PrefixPath 2.1.4 RequestRateLimiter 2.1.5 Retry 2.2 自定义过滤器 1. 过滤器的作用 过滤器通常用于拦截、处理或修改数据流和事…

Redis 快速搭建与使用

文章目录 1. Redis 特性1.1 多种数据类型支持1.2 功能完善1.3 高性能1.4 广泛的编程语言支持1.5 使用简单1.6 活跃性高/版本迭代快1.7 I/O 多路复用模型 2. Redis发展历程3. Redis 安装3.1 源码安装3.1.1 下载源码包3.1.2 解压安装包3.1.3 切换到 Redis 目录3.1.4 编译安装 3.2…

slf4j+logback源码加载流程解析

Logger log LoggerFactory.getLogger(LogbackDemo.class);如上述代码所示&#xff0c;在项目中通常会这样创建一个Logger对象去打印日志。 然后点进去&#xff0c;会走到LoggerFactory的getILoggerFactory方法&#xff0c;如下代码所示。 public static ILoggerFactory getILo…

缓存cache和缓冲buffer的区别

近期被这两个词汇困扰了&#xff0c;感觉有本质的区别&#xff0c;搜了一些资料&#xff0c;整理如下 计算机内部的几个部分图如下 缓存&#xff08;cache&#xff09; https://baike.baidu.com/item/%E7%BC%93%E5%AD%98 提到缓存&#xff08;cache&#xff09;&#xff0c;就…

<PDF-Pics> support

If get any questions,email me caohechunhotmail.com