定时器的使用及实现

在Java中,定时器(Timer)是一个用于执行任务的工具类。它可以安排任务在指定的时间点执行,或者按照指定的时间间隔周期性地执行。

1. Timer类

Timer类位于java.util包中,它提供了一种简单而便利的方式来安排以后的任务执行。Timer类的核心思想是创建一个后台线程,在指定的时间点执行任务或者按照指定的时间间隔周期性地执行任务。以下是Timer类的几个重要概念:

  • Timer对象:通过Timer timer = new Timer();语句创建一个Timer对象,用于安排任务的执行。
  • TimerTask对象:TimerTask是一个抽象类,表示要执行的任务。通常需要创建一个继承自TimerTask的具体任务类,并重写其中的run()方法,定义要执行的任务逻辑。

安排任务的执行
使用Timer类安排任务的执行通常需要以下步骤:

  1. 创建Timer对象:通过Timer timer = new Timer();语句创建一个Timer对象。
  2. 创建任务:创建一个继承自TimerTask类的具体任务类,重写其中的run()方法,定义要执行的任务逻辑。
  3. 安排任务执行:使用schedule()方法安排任务的执行。可以通过指定任务、延迟时间和执行周期等参数来安排任务的执行时间和频率。例如:timer.schedule(task, delay, period);

具体任务类需要重写TimerTask类中的run()方法,定义要执行的任务逻辑。例如:

class MyTask extends TimerTask {
    public void run() {
        // 执行具体的任务逻辑
        System.out.println("Hello");
    }
}

最后,调用timer.schedule()方法启动定时器,将任务安排到定时器中进行执行。

public class ThreadDemo25 {
    public static void main(String[] args) {
        Timer timer = new Timer();
        TimerTask t = new TimerTask() {
            @Override
            public void run() {
                System.out.println("Ting");
            }
        };
        
        //3000ms后执行 t
        timer.schedule(t, 3000);
    }
}

一个Timer可以加入多个任务:

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

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

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

注意:Timer对象会创建一个线程,并且这个线程是一个前台线程。

注意:Timer这个线程不会主动结束,除非调用 cansel 方法

2. 实现一个Timer类

 思考一下 Timer 需要包含哪些内容

1. 需要有一个能数组/队列来存储要执行的任务

2. 需要一个线程负责执行任务

由于,每个任务执行的时间不同,我们可以让先执行的任务排在前面,每次只用看第一个任务是否到达执行时间即可,所以我们可以使用一个优先级队列来存储任务。

class MyTimer {
    //存储用于执行的任务
    private PriorityQueue<> q = new PriorityQueue<>();
    //负责执行的线程
    Thread t = null;

    public MyTimer() {
        //在构造方法中实现并运行线程
        t = new Thread(() -> {

        });
        t.start();
    }

    public void schedule(Runnable runnable, int daley) {
        //用来添加任务,和执行的时间
    }
}

我们已经有了Timer的大概框架,我们还需要实现一个MyTimerTask 来关联任务和执行时间,放在我们的优先级队列中:

class MyTimerTask implements Comparable<MyTimerTask> {
    //执行任务的时间,这里约定为一个毫秒级时间戳
    private long time;
    //任务,这里也可以直接实现Runnab接口
    private Runnable runnable;

    public MyTimerTask(Runnable runnable, int daley) {
        this.runnable = runnable;
        //将相对时间转化为时间戳
        time = System.currentTimeMillis() + daley;
    }

    public long getTime() {
        return time;
    }

    //在这里提供执行任务的方法
    public void run() {
        runnable.run();
    }

    //由于我们的MyTimerTask 是需要放在优先级队列中的,所以需要可比较
    //于是我们可以实现Comparable接口,以执行间隔时间比较大小    
    @Override
    public int compareTo(MyTimerTask o) {
        return (int)(this.time - o.time);
    }
}

现在我们可以进一步实现schedule方法:

   public void schedule(Runnable runnable, int daley) {
        MyTimerTask task = new MyTimerTask(runnable, daley);
        q.offer(task);//添加到队列中
    }

现在我们只需要实现MyTimer中具体的线程 t 即可:

        public MyTimer() {
            t = new Thread(() -> {
                while(true) {
                    //队列为空
                    if (q.isEmpty()) {
                        continue;
                    }

                    MyTimerTask run = q.peek();
                    long CurTime = System.currentTimeMillis();

                    if (run.getTime() <= CurTime) {
                        q.poll();
                        //开始执行
                        run.run();
                    }else {
                        //还未到执行时间
                        continue;
                    }
                }
            });
            t.start();
        }

在上述代码中我们实现了 t 线程的大概框架,我们发现,t 线程中存在一个 poll 操作,而上面的schedule方法中存在 offer 操作,并且这个操作是在 main线程中完成的,两个线程都在对同一个队列进行读写操作,可能会有线程安全问题,所以我们 需要给,t线程中和 schedule 方法中都加上锁。

    public void schedule(Runnable runnable, int daley) {
        MyTimerTask task = new MyTimerTask(runnable, daley);
        synchronized (locker) {
            q.offer(task);
        }
    }

 

    public MyTimer() {
        t = new Thread(() -> {
            while(true) {
                synchronized (locker) {
                    //队列为空
                    if (q.isEmpty()) {
                        continue;
                    }

                    MyTimerTask run = q.peek();
                    long CurTime = System.currentTimeMillis();

                    if (run.getTime() <= CurTime) {
                        q.poll();
                        //开始执行
                        run.run();
                    }else {
                        //还未到执行时间
                        countine;
                    }
                }
            }
        });
        t.start();
    }

与此同时,如果队列为空,t 线程中会直接 coutine 进入下一次 循环,这个循环速度是非常快的,有可能 ,t 线程释放锁之后有拿到锁了,导致线程饿死,所以我们 这里可以使用 wait 等待,并在schedule 中 添加任务成功后,调用notify,同理:如果某个任务的执行时间没到,也会一直循环,所以我们可以把下面 else 中的 continue 换成 notify :

    public void schedule(Runnable runnable, int daley) {
        MyTimerTask task = new MyTimerTask(runnable, daley);
        synchronized (locker) {
            q.offer(task);
            locker.notify();
        }
    }
    public MyTimer() {
        t = new Thread(() -> {
            while(true) {
                synchronized (locker) {
                    //队列为null阻塞等待
                    while (q.isEmpty()) {
                        try {
                            locker.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }

                    MyTimerTask run = q.peek();
                    long CurTime = System.currentTimeMillis();

                    if (run.getTime() <= CurTime) {
                        q.poll();
                        //开始执行
                        run.run();
                    }else {
                        //还未到执行时间,等待notify唤醒,或者到执行时间
                        try {
                            locker.wait(run.getTime() - CurTime);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
        });
        t.start();
    }

分析一遍上面代码:进入 t 线程,如果队列为 空 ,阻塞等待,直到调用 schedule 添加任务后 notify 取消阻塞,然后进入下面,判断是否到了执行时间,如果到了就出队列,然后执行,没到执行时间则阻塞等待,直到到达执行时间,或者添加了新任务 调用notify 取消阻塞,注意这里:添加新任务后,这个任务的执行时间可能为当前队列中最早的,所以要进入下一次循环重新peek,确保拿到的一定是队列中最早执行的任务。

下面来看一下整体代码和执行效果:

class MyTimer {
    //存储用于执行的任务
    private PriorityQueue<MyTimerTask> q = new PriorityQueue<>();
    //负责执行的线程
    Thread t = null;
    Object locker = new Object();
    public MyTimer() {
        t = new Thread(() -> {
            while(true) {
                synchronized (locker) {
                    //队列为null阻塞等待
                    while (q.isEmpty()) {
                        try {
                            locker.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }

                    MyTimerTask run = q.peek();
                    long CurTime = System.currentTimeMillis();

                    //未到执行时间阻塞等待
                    if (run.getTime() <= CurTime) {
                        q.poll();
                        //开始执行
                        run.run();
                    }else {
                        //还未到执行时间,等待notify唤醒,或者到执行时间
                        try {
                            locker.wait(run.getTime() - CurTime);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
        });
        t.start();
    }

    public void schedule(Runnable runnable, int daley) {
        MyTimerTask task = new MyTimerTask(runnable, daley);
        synchronized (locker) {
            q.offer(task);
            locker.notify();
        }
    }
}

class MyTimerTask implements Comparable<MyTimerTask> {
    //执行任务的时间
    private long time;
    //任务
    private Runnable runnable;

    public MyTimerTask(Runnable runnable, int daley) {
        this.runnable = runnable;
        time = System.currentTimeMillis() + daley;
    }
    public long getTime() {
        return time;
    }
    public void run() {
        runnable.run();
    }

    @Override
    public int compareTo(MyTimerTask o) {
        return (int)(this.time - o.time);
    }
}
public class ThreadDemo26 {
    public static void main(String[] args) {
        MyTimer timer = new MyTimer();

        timer.schedule(() -> {
            System.out.println("3");
        }, 3000);

        timer.schedule(() -> {
            System.out.println("2");
        }, 2000);

        timer.schedule(() -> {
            System.out.println("1");
        }, 1000);
    }
}

 

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

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

相关文章

AR + 通信,虚实结合让工作协同从线上到「现场」

在数字经济无所不在的当下&#xff0c;千行百业都与数智化办公接轨并因其实现转型升级。关注【融云 RongCloud】&#xff0c;了解协同办公平台更多干货。 升级的背后&#xff0c;是利用技术把工作用更自然的方式连接起来&#xff0c;让整个工作流协同更顺、体验更好。 而其中…

Dijkstra(迪杰斯特拉)算法

Dijkstra(迪杰斯特拉)算法的思想是广度优先搜索&#xff08;BFS&#xff09; 贪心策略。 是从一个顶点到其余各顶点的最短路径算法&#xff0c;节点边是不各自不同的权重&#xff0c;但都必须是正数 如果是负数&#xff0c;则需要 Bellman-Ford 算法 如果想求任意两点之间的距离…

占用站点资源,无法正常登录?这个功能帮助解决

在企业里随着PDM用户的增加PDM管理员是否发现原本的站点已经不够用出现部分用户占用站点资源导致其他用户无法正常登录导致该问题无法解决&#xff0c;本篇介绍PDM自动下线的功能助力企业解决问题&#xff0c;更好的帮助企业完成PDM的正常使用 今天我给大家带来的就是SOLIDWOR…

外网的maven项目转移到内网操作的步骤

1、新起一个仓库路径testRep&#xff0c;idea 引用的maven里的setting.xml里仓库配置修改成刚才建的路径&#xff0c;目的把需要的jar全部下载到那个文件夹里 2、项目打压缩包&#xff0c;刚才仓库文件夹打压缩包&#xff0c;并复制到内网电脑 3、内网电脑idea引入项目 4、修改…

【重点】【矩阵】48. 旋转图像

题目 参考答案 法1&#xff1a;辅助矩阵 class Solution {public void rotate(int[][] matrix) {int n matrix.length;int[][] newMatrix new int[n][];for (int i 0;i < n; i) {newMatrix[i] matrix[i].clone();}for (int i 0; i < n; i) {for (int j 0; j <…

PD-1、BRAF和MEK联合抑制BRAFV600E结直肠癌癌症的2期试验

今天给同学们分享一篇文章“Combined PD-1, BRAF and MEK inhibition in BRAFV600E colorectal cancer: a phase 2 trial”&#xff0c;这篇文章发表在Nat Med期刊上&#xff0c;影响因子为82.9。 结果解读&#xff1a; MAPK抑制增强BRAF V600E CRC的免疫反应 作者之前在BRAF…

图的深度优先搜索(数据结构实训)

题目&#xff1a; 图的深度优先搜索 描述&#xff1a; 图的深度优先搜索类似于树的先根遍历&#xff0c;是树的先根遍历的推广。即从某个结点开始&#xff0c;先访问该结点&#xff0c;然后深度访问该结点的第一棵子树&#xff0c;依次为第二顶子树。如此进行下去&#xff0c;直…

彩色成像的基础和应用 原理 Principles(一)

下面我将不定期尽可能出一系列&#xff08;我觉的非常好&#xff09;翻译的文章来解释颜色这们学科。【下图为此次翻译的书籍封面】 Introduction: 颜色是一种与光的物理学&#xff0c;物质的化学&#xff0c;物体的几何特性以及人…

【【RGB LCD 彩条显示实验 ---1】】

RGB LCD 彩条显示实验 —1 TFT-LCD 的全称是 Thin Film Transistor-Liquid Crystal Display&#xff0c;即薄膜晶体管液晶显示屏&#xff0c;它显示的每个像素点都是由集成在液晶后面的薄膜晶体管独立驱动&#xff0c;因此 TFT-LCD 具有较高的响应速度以及较好的图像质量。 我…

基于JAVA+SpringBoot+微信小程序的宠物领养平台

✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背景介绍&#xff1a; 随着人们生活水平的提…

Linux挂载配置本地yum源

1.vi /etc/yum.repos.d/redhat.repo 2. [baseos] namebaseos baseurlfile:///mnt/BaseOS #enabled:默认为1 enabled1 gpgcheck0 [appstream] nameappstream baseurlfile:///mnt/AppStream enabled1 gpgcheck0 3. mount /dev/sr0 /mnt/ 4.yum clean all 5.yum makecache

Java 简易版 UDP 多人聊天室

服务端 import java.io.*; import java.net.*; import java.util.ArrayList; public class Server{public static ServerSocket server_socket;public static ArrayList<Socket> socketListnew ArrayList<Socket>(); public static void main(String []args){try{…

校园跑腿小程序源码系统 源码完全开源可二开,在线下单 附带完整的搭建教程

随着互联网的快速发展&#xff0c;人们的生活方式也在不断改变。特别是在校园内&#xff0c;由于学习、生活等各种原因&#xff0c;学生们需要处理许多琐碎的事情。而校园跑腿服务正是在这样的背景下应运而生&#xff0c;它能够为学生们提供便捷、快速、个性化的服务&#xff0…

LeetCode题:931下降路径最小和

目录 一、题目要求 二、解题思路 &#xff08;1&#xff09;状态表示 &#xff08;2&#xff09;状态转移方程 &#xff08;3&#xff09;初始化 &#xff08;4&#xff09;填表顺序 &#xff08;5&#xff09;返回值 三、代码 一、题目要求 931. 下降路径最小和 给你…

统信UOS_麒麟KYLINOS上使用WeekToDo

原文链接&#xff1a;统信UOS/麒麟KYLINOS上使用WeekToDo hello&#xff0c;大家好啊&#xff0c;今天给大家带来一篇在统信UOS/麒麟KYLINOS上使用WeekToDo的介绍。在忙碌的工作和生活中&#xff0c;有效地管理时间和任务是非常重要的。WeekToDo作为一个免费和开源的每周计划器…

python实现pdf转word、word转pdf

我的博客 文章首发于公众号&#xff1a;小肖学数据分析 Python自动化办公通常对常用的办公软件文档格式进行操作&#xff0c;比如Word和PDF。 很多软件都需要付费&#xff0c;作为程序员&#xff0c;怎么可能付费。 下面是一个简单示例&#xff0c;如何在Python中将Word文档…

Java网络编程——非阻塞通信

对于用ServerSocket以及Socket编写的服务器程序和客户程序&#xff0c;它们在运行过程中常常会阻塞。例如当一个线程执行ServerSocket的accept()方法时&#xff0c;假如没有客户连接&#xff0c;该线程就会一直等到有了客户连接才从accept()方法返回。再例如当线程执行Socket的…

跨境电商做自己养号做测评,收货地址怎么解决?

近期有很多朋友问我&#xff0c;自己在做自媒体的时候&#xff0c;物流方面的问题该怎么解决&#xff1f;其实这个问题很简单&#xff0c;下面我就给大家分享一些解决物流问题的方法。 首先&#xff0c;如果你是自己发货&#xff0c;可以选择直接找物流商购买单号或者发空包。这…

基于ssm人事管理信息系统论文

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本人事管理信息系统就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据信息…

题目:谈判(蓝桥OJ 545)

题目描述&#xff1a; 解题思路&#xff1a; 本题采用贪心的思想&#xff0c;与蓝桥的合并果子题思路一样。可以使用优先对列&#xff0c;输入进去后自动排序。将两个最小的合并再放入对列中&#xff0c;并将值加入到ans&#xff0c;最终结果即ans。如下图&#xff1a;xy为4&a…