Java模拟实现定时器

定时器的基本使用

Java中的定时器是Timer,通过调用Timer对象的schedule方法传入任务和延迟时间。如下:

java 复制代码
    public static void main1(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask(){
            @Override
            public void run(){
                System.out.println("1000");
            }
        },1000);
    }

以上代码表示:在1秒后打印 1000.

运行以上代码后发现,程序一直没有退出,说明在timer对象中创建了一个线程,这个线程正在等待我们派发任务,所以一直没有退出。

定时器的模拟实现

要模拟实现定时器就需要确认3个东西:1. 创建对象来存储用户传入的任务和延迟时间。2. 使用某种数据结构来存储用户传入的任务。3. 需要一个线程不断的扫描这个数据结构中是否有任务未被执行。

那么使用哪种数据结构呢? 优先级队列是是被考虑的,因为这些定时任务的优先级就是按照延迟时间进行排序的,延迟时间越短,那么优先级就越高。

由于优先级队列中的元素是可比较的,故必须对存放延迟时间和任务的类实现Comparable接口,重写Comparator方法,通过时间戳进行比较。

所以我们创建一个MyTimerTask对象来存储用户的任务和延迟时间。如下:

java 复制代码
class MyTimerTask implements Comparable<MyTimerTask>{
    // 任务执行的绝对时间
    private Long time;
    // 任务
    private Runnable runnable;
    MyTimerTask(Runnable runnable ,Long time){
        this.runnable = runnable;
        this.time = System.currentTimeMillis()+time;
    }
    public Long getTime(){return this.time;}
    public Runnable getRunnable(){return this.runnable;}

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

注意:这里没有记录用户传入的延迟时间,而是直接记录这个任务要完成的绝对的时间戳。

接下来就需要创建MyTimer类了。这个类需要包含:

  1. 优先级队列
  2. 线程(用于扫描优先级队列中是否有任务待执行)
  3. schedule方法(第一个参数,Runnable对象
java 复制代码
class MyTimer{
    private static volatile Object locker = new Object();
    // 优先级队列
    PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
    public void schedule(Runnable runnable,Long time){
        synchronized (locker){
            // 创建对象
            queue.offer(new MyTimerTask(runnable,time));
            // 创建好对象之后,就唤醒。
            locker.notify();
        }
    }
    // 搞一个扫描线程
    public MyTimer(){

        Thread t = new Thread(()->{
            // 需要不停的扫描是否有任务。
            while(true){
                synchronized (locker){
                    while(queue.isEmpty()){
                        // 为空,则需要等待
                        try {
                            locker.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    MyTimerTask task = queue.peek();
                    Long time = task.getTime();
                    Runnable runnable = task.getRunnable();
                    long currentTime = System.currentTimeMillis();
                    // 满足,
                    if(currentTime >= time){
                        runnable.run();
                        queue.poll();
                    }else {
                    try {
                        locker.wait(time-currentTime);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    }
                }
            }
        });
        t.start();
    }
}

注意:这个扫描线程需要不停的去扫描优先级队列中受否有任务待执行,所以需要使用while循环。

注意:在判断当前的时间是否是大于定时器执行的时间时,如果不满足,那么不需要进行下一次的while循环,可以等待time-currentTime的时间。这样就可以防止忙等的情况发生。

直到优先级队列中的第一个任务完成之后,这个线程才会继续执行优先级队列中的下一个任务。这是通过MyTimerTask是通过时间戳比较器来实现的。

注意:为了防止指令重排序的情况发生和内存不可见性,建议对加锁中的代码块中要被操作的变量加上volatile关键字。防止出现意料之外的错误。

相关推荐
程序员三明治8 小时前
【Java基础】深入 String:为什么它是不可变的?从底层原理到架构设计
java·开发语言·java基础·string·不可变
沐知全栈开发8 小时前
jEasyUI 树形菜单添加节点
开发语言
怎么追摩羯座8 小时前
使用PyCharm调用Ollama,制作智能问答机器人
ide·python·pycharm·大模型·ollama
珂朵莉MM8 小时前
2025年睿抗机器人开发者大赛CAIP-编程技能赛-本科组(国赛)解题报告 | 珂学家
java·人工智能·算法·机器人·无人机
怎么追摩羯座8 小时前
win安装使用PyCharm
ide·python·pycharm
观无8 小时前
雷塞运动控制(DMC3800)C#基础应用案例分享
开发语言·c#
indexsunny9 小时前
互联网大厂Java求职面试实战:微服务与Spring Boot在电商场景中的应用
java·数据库·spring boot·微服务·kafka·hibernate·电商
w-w0w-w9 小时前
C++中vector的操作和简单实现
开发语言·数据结构·c++
Larry_Yanan9 小时前
Qt安卓开发(一)Qt6.10环境配置
android·开发语言·c++·qt·学习·ui