定时器的使用及实现

news2024/11/27 16:09:39

在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);
    }
}

 

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

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

相关文章

1.4 场景设计精要

一、场景主题确定 设计游戏场景首先明确游戏发生的时间地点等时代背景。通过对玩家动线的设计&#xff0c;功能模型的合理布局构建出场景的基本骨架。利用光影效果和色彩变化烘托场景氛围。 市场上常见的主题场景&#xff1a;剑侠、科幻、废墟、魔幻等 二、场景风格确定 大类分…

深入理解mysql的explain命令

1 基础 全网最全 | MySQL EXPLAIN 完全解读 1.1 MySQL中EXPLAIN命令提供的字段包括&#xff1a; id&#xff1a;查询的标识符。select_type&#xff1a;查询的类型&#xff08;如SIMPLE, PRIMARY, SUBQUERY等&#xff09;。table&#xff1a;查询的是哪个表。partitions&…

Vue学习计划-Vue2--Vue核心(七)生命周期

抛出问题&#xff1a;一进入页面就开启一个定时器&#xff0c;每隔1秒count就加1&#xff0c;如何实现 示例&#xff1a; <body> <div id"app">{{ n }}<button click"add">执行</button> </div><script>let vm new …

西工大计算机学院计算机系统基础实验一(函数编写15~17)

还是那句话&#xff0c;稳住心态&#xff0c;稳住心态&#xff0c;稳住心态。心里别慌&#xff0c;心里别慌&#xff0c;心里别慌。 第15题&#xff0c;howManyBits&#xff0c;返回用二进制补码形式表示x所需的最小二进制位数。比如howManyBits(12) 5&#xff0c;12可以被表…

高级实现Java的七大热门技术框架解析源码特性分析

Java是一门广泛应用的编程语言&#xff0c;拥有众多热门技术框架。本文将通过解析源码和特性分析&#xff0c;带你深入了解Java的七大热门技术框架&#xff0c;并提供相关示例代码。 一、Spring框架 Spring是Java最流行的开发框架之一&#xff0c;提供了依赖注入&#xff08;D…

电商早报 | 12月7日| 阿里巴巴分红179亿,破历史记录

阿里巴巴将派发25亿美元年度股息 12月6日消息&#xff0c;阿里巴巴发布公告&#xff0c;将向截至2023年12月21日香港时间及纽约时间收市时登记在册的普通股持有人和美国存托股持有人&#xff0c;就2023财年首次派发年度股息&#xff0c;金额分别为每股普通股0.125美元或每股美…

mysql知识分享(包含安装卸载)(一)

如果博客有错误&#xff0c;请佬指正。 目录 注意&#xff1a;打开cmd时要有管理员身份打开&#xff0c;重要 为何使用数据库&#xff1f; 数据库的相关概念 关系型数据库 关系型数据库设计规则 表&#xff0c;记录&#xff0c;字段 表的关联关系 一对一关联 一对多关系 …

如何衡量和提高测试覆盖率?

衡量和提高测试覆盖率&#xff0c;对于尽早发现软件缺陷、提高软件质量和用户满意度&#xff0c;都具有重要意义。如果测试覆盖率低&#xff0c;意味着用例未覆盖到产品的所有代码路径和场景&#xff0c;这可能导致未及时发现潜在缺陷&#xff0c;代码中可能存在逻辑错误、边界…

[Geek Challenge 2023] web题解

文章目录 EzHttpunsignn00b_Uploadeasy_phpEzRceezpythonezrfi EzHttp 按照提示POST传参 发现密码错误 F12找到hint&#xff0c;提示./robots.txt 访问一下&#xff0c;得到密码 然后就是http请求的基础知识 抓包修改 最后就是 我们直接添加请求头O2TAKUXX: GiveMeFlag 得到…

vue中的动画组件使用及如何在vue中使用animate.css

“< Transition >” 是一个内置组件&#xff0c;这意味着它在任意别的组件中都可以被使用&#xff0c;无需注册。它可以将进入和离开动画应用到通过默认插槽传递给它的元素或组件上。进入或离开可以由以下的条件之一触发&#xff1a; 由 v-if 所触发的切换由 v-show 所触…

用 C 写一个卷积神经网络

用 C 写一个卷积神经网络 深度学习领域最近发展很快&#xff0c;前一段时间读transformer论文《Attention Is All You Need》时&#xff0c;被一些神经网络和深度学习的概念搞得云里雾里&#xff0c;其实也根本没读懂。发现深度学习和传统的软件开发工程领域的差别挺大&#xf…

数据结构:图文详解双向链表的各种操作(头插法,尾插法,任意位置插入,查询节点,删除节点,求链表的长度... ...)

目录 一.双向链表的概念 二.双向链表的数据结构 三.双向链表的实现 节点的插入 头插法 尾插法 任意位置插入 节点的删除 删除链表中第一次出现的目标节点 删除链表中所有与关键字相同的节点 节点的查找 链表的清空 链表的长度 四.模拟实现链表的完整代码 前言&am…

多人群聊代码

服务端 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{…

5G - NR物理层解决方案支持6G非地面网络中的高移动性

文章目录 非地面网络场景链路仿真参数实验仿真结果 非地面网络场景 链路仿真参数 实验仿真结果 Figure 5 && Figure 6&#xff1a;不同信噪比下的BER和吞吐量 变量 SISO 2x2MIMO 2x4MIMO 2x8MIMOReyleigh衰落、Rician衰落、多径TDL-A(NLOS) 、TDL-E(LOS)(a)QPSK (b)16…

echarts环形饼图

效果示例 代码汇总 pieCharts() {let data [];const providerResult [{name: 智诺, value: 23},{name: 海康, value: 5},{name: 大华, value: 5}, {name: 云科, value: 23},{name: 四信, value: 22},{name: 九物, value: 22}]let charts echarts.init(document.getElemen…

700G全球30米高程DEM原始数据

这里&#xff0c;为大家分享700G的全球30米高程原始数据。 全球30米高程覆盖范围 NASA全球30米SRTM高程DEM数据范围在南纬56度到北纬61度范围之间&#xff0c;共分为14520个区域范围。 每个区域范围在经纬度方向的跨度均为1度大小&#xff0c;将该接图表在微图中与影像叠加之…

【C++】如何优雅地把二维数组初始化为0

2023年12月7日&#xff0c;周四上午 目录 为什么要初始化二维数组不优雅的初始化方式&#xff1a;使用两个for循环优雅的初始化方式一&#xff1a;使用初始化列表优雅的初始化方式二&#xff1a;使用memset函数 为什么要初始化二维数组 如果不初始化二维数组&#xff0c;那么…

海云安参与制定《信息安全技术 移动互联网应用程序(App)软件开发工具包(SDK)安全要求》标准正式发布

近日&#xff0c;由TC260&#xff08;全国信息安全标准化技术委员会&#xff09;归口 &#xff0c;主管部门为国家标准化管理委员会&#xff0c;深圳海云安网络安全技术有限公司&#xff08;以下简称“海云安”&#xff09;等多家相关企事业单位共同参与编制的GB/T 43435-2023《…

在Mac上安装Windows应用程序的简便方法:CrossOver for Mac

对于许多Mac用户来说&#xff0c;有时候他们可能需要使用一些只有在Windows上才能找到的应用程序。以前&#xff0c;解决这个问题的方法是通过安装Windows虚拟机或使用双系统来在Mac上运行Windows应用程序。但这些方法需要额外的硬件资源和时间来配置&#xff0c;并且可能会导致…

JVM GUI可视化监控及诊断工具

工具既述 使用命令行工具或组合能帮您获取目标Java应用性能相关的基础信息&#xff0c;但它们存在下列局限&#xff1a; 无法获取方法级别的分析数据&#xff0c;如方法间的调用关系、各方法的调用次数和调用时间等&#xff08;这对定位应用性能瓶颈至关重要&#xff09;。要…