Java 多线程6——计时器Timer的使用 + 详细代码模拟实现 + 代码优化


前言

本人是一个刚刚上路的IT新兵,菜鸟!分享一点自己的见解,如果有错误的地方欢迎各位大佬莅临指导,如果这篇文章可以帮助到你,劳请大家点赞转发支持一下!

本篇文章为大家带来的仍然是多线程编程,计时器是许多场景都会应用到的一个非常方便快捷实用的类。


一、定时器是什么?

🦉定时器,顾名思义他的功能类似于一个闹钟,但又比闹钟更加智能便捷。

🎗️在手机上,你可以设置某个时间的闹钟,并备注上内容,以便提醒自己这个时间要干什么。

🧑🏻‍💻 编程里的定时器,也是如此,他需要你先设置"需要执行的代码",到了这个时间程序就会自动帮你执行这一段代码。

换句话说, 定时器就是属于你的超级免费劳动力,你给他布置什么时间去执行什么任务,时间到了他就会自动去执行对应的任务。


二、定时器如何使用

标准库提供了一个Timer类,这个类就是计时器。

而像里面添加闹钟的方法是schedule方法 ,这也是该类的核心方法。


在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


1️⃣ 第一个参数:TimerTask类型的task,这个参数实际上与一个线程无异,所以要重写run方法

2️⃣ 第二个参数:long类型的delay,就是延迟执行的时间。意思就是多少ms后执行第一个参数这个线程。 单位:ms


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

在这里插入图片描述


🎪 可以看到,三个任务都执行完以后,程序并没有结束,是因为Timer类内部有一个前台线程,会阻止进程结束。


三、代码模拟实现定时器

1.理论准备

在这里插入图片描述
Timer内部有两个核心的成员:
1️⃣ TaskQueue类型的queue。
2️⃣ TimerThread类型的thread。


thread是一个Timer内置的前台线程,他会去按时执行定时器中的任务。
在这里插入图片描述
在这里插入图片描述
核心方法是 使用while循环让他按时去执行计时器中已有的任务与后续添加的任务

这也就是为什么上面,计时器中所有任务都执行完毕,进程仍没有结束的原因。

所以咱们自己写一个定时器, 也需要一个线程去按时执行任务,并且不确定后续会不会有新添加的任务,所以在线程里也要搞一个死循环


queue是用来存放线程任务的队列,而 定时器的优先级不是先来后到,而是基于时间快慢

就比如:

你想一小时后吃早饭,定了一个🍙吃饭闹钟。
你又想半小时后起床,定了一个🛏️起床闹钟。
那么起床闹钟一定比吃饭闹钟先响!

所以存储任务的数据结构我们选择使用一个优先级队列。因为涉及到了线程,我们就 使用一个线程安全的带有优先级的阻塞队列PriorityBlockingQueue


上述两个内容都了解之后,我们还需要一个类来 存储任务 延迟的时间 。与schedule方法中的TimerTask类似

该类需要两个成员变量:

1️⃣一个Runnable类型来存放任务。
2️⃣一个long类型来确定执行时间,以便放入队列中。(六点的两小时后 == 七点的一小时后。)存放该任务的时间+延迟执行的时间==该任务的执行时间


2.代码实现

任务类

class MyTask implements Comparable<MyTask>{
    // 存储任务
    public Runnable runnable;

    // 延迟的时间
    public long time;

    public MyTask(Runnable runnable, long dely) {
        this.runnable = runnable;
        //获取当前时间戳并+dely,作为当前任务实际执行的时间戳
        this.time = dely + System.currentTimeMillis();
    }

    // 重写compareTo方法,让先执行的任务在优先级阻塞队列的队首
    @Override
    public int compareTo(MyTask o) {
        return (int)(this.time - o.time);
    }
}

计时器

public class MyTimer {

    // 创建优先级阻塞队列,计时器的核心数据结构
    private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();

    public void schedule(Runnable runnable,long delay) {
        // 创建任务
        MyTask myTask = new MyTask(runnable,delay);
        // 将任务放进计时器
        queue.put(myTask);
    }

    public MyTimer() {
        // 内置线程执行计时器中的任务,并等待添加任务
        Thread thread = new Thread(() -> {
            while (true) {
                try {
                    // 取出计时器中最优先执行的任务
                    // 优先级阻塞队列,不用担心队列为空抛出异常
                    // 队列为空则会阻塞等待
                    MyTask myTask = queue.take();
                    // 判断是否到达执行时间
                    if(System.currentTimeMillis() >= myTask.time) {
                        // 到达执行
                        myTask.runnable.run();
                    } else {
                        // 未到达再放回计时器
                        queue.put(myTask);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();
    }
}

运行结果:
在这里插入图片描述


3.🧑🏻‍💻优化代码

上述的代码运行结果正确,没有什么问题了。
🧑🏻‍💻🧑🏻‍💻🧑🏻‍💻但是还可以进行优化。


忙等问题
如果这个队列里最先执行的任务也要一个小时后才执行,也就是说程序在这一个小时中就要一直重复 取出任务 判断时间 放回任务 这一系列操作,期间会一直占用CPU资源。

CPU的执行效率之高,执行速度之快,一秒钟可以执行上亿次语句,那么忙等问题就浪费了天文数字量级的CPU资源

💉💉💉那么如何解决这个忙等问题呢。
很简单,只需要使用前面分享过的 wait方法


但是就会出现一个新的问题:

还是上述情景, 添加了wait方法后,应该是取出这个任务后,让当前线程等待一小时,这期间不再占用CPU资源

如果我又插入了一个十分钟后就需要执行的任务呢??
那么此时线程还在等待中,不会被CPU调度执行,那么这个十分钟后执行的任务就无法被及时执行。

💉💉💉因此每次插入一个新的任务都要用 notify方法 来唤醒线程。

优化后代码:


public class MyTimer {

    // 锁对象
    Object locker = new Object();

    // 创建优先级阻塞队列,计时器的核心数据结构
    private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();

    public void schedule(Runnable runnable,long delay) {
        // 创建任务
        MyTask myTask = new MyTask(runnable,delay);
        // 将任务放进计时器
        queue.put(myTask);
        synchronized (locker) {
        	// 叫醒线程
            locker.notify();
        }
    }

    public MyTimer() {
        // 内置线程重复执行计时器中的任务
        Thread thread = new Thread(() -> {
            while (true) {
                try {
                    synchronized (locker) {
                        // 取出计时器中最优先执行的任务
                        MyTask myTask = queue.take();
                        // 判断是否到达执行时间
                        if (System.currentTimeMillis() >= myTask.time) {
                            // 到达执行
                            myTask.runnable.run();
                        } else {
                            // 未到达再放回计时器
                            queue.put(myTask);
                            // 让线程等待相应时间
                            locker.wait(myTask.time - System.currentTimeMillis());
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();
    }
}

总结

以上就是今天要分享的内容了,本文介绍了定时器是什么,原理,以及代码模拟实现,定时器也是多线程编程中常常用的工具,希望大家都能掌握。

路漫漫不止修身,也养性。

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值