首页 > 编程语言 >【JavaEE精炼宝库】多线程(7)定时器

【JavaEE精炼宝库】多线程(7)定时器

时间:2024-06-20 13:01:09浏览次数:12  
标签:定时器 schedule JavaEE Runnable 线程 new 多线程 public

目录

一、定时器的概念

二、标准库中的定时器

三、自己实现一个定时器

3.1 MyTimerTask 实现:

3.2 MyTimer 实现:


一、定时器的概念

定时器也是软件开发中的⼀个重要组件。类似于一个 "闹钟"。达到一个设定的时间之后,就执行某个指定好的代码(可以用来完成线程池里面的非核心线程的超时回收)。

定时器是一种实际开发中非常常用的组件。 比如网络通信中,如果对方 500ms 内没有返回数据,则断开连接尝试重连。比如⼀个 Map,希望里面的某个 key 在 3s 之后过期(自动删除)。类似于这样的场景就需要用到定时器。

二、标准库中的定时器

标准库中提供了⼀个 Timer 类。Timer 类的核心方法为 schedule。

schedule 包含两个参数。第⼀个参数指定即将要执行的任务代码,第二个参数指定多长时间之后 执行(单位为毫秒)。如下:

其中第一个参数 TimerTask 是一个抽象类,本质上还是实现了 Runnable 接口,所以我们就可以把它当作 Runnable 来使用即可。 

• 使用演示:

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

案例效果演示:

会间隔对应的时间打印。

三、自己实现一个定时器

我们在写代码之前要想好我们的需求是什么,也就是我们要实现什么,我们定时器的需求:1. 是能够延时执行任务 / 指定时间执行任务。2. 能够管理多个任务。

3.1 MyTimerTask 实现:

首先我们先实现任务,可以实现 Runnable 接口,或者采用把 Runnable 作为类参数,来进行实现。这里我们采用把 Runnable 作为类参数来进行实现。为了起到延时的效果,我们还需要一个 time 参数来保存绝对的时间。

为什么需要绝对时间呢?

答:这个其实很好理解,我们举一个栗子来解释:假如现在是早上 9 点,领导让你 1 小时之后去找他,也就是说我们应该在早上 10 点左右去找他,但是如果我们只是记录 1 个小时,那么随着时间的推移,我们不能够知道这个 1 小时之后,是哪个时间点的。当然我们可以采用倒计时的方法来实现,但是这样我们还要不停的维护,这个倒计时,倒不如直接记录绝对时间来的简单。

这里我们还需要实现 Comparable 接口,为什么还需要实现这个接口呢?

答:在 Timer 类中,任务不仅仅只有一个,且绝对时间大小与进入队列的顺序没有绝对关系,那么我们如何在队列中快速找到绝对时间最小的任务呢(如果绝对时间最小的任务都不满足执行时间,那么后面的任务绝对也不满足)?显然需要使用到优先级队列(小根堆)来存储任务,但是我们自定义的类不能比较,所以我们需要实现 Comparable 接口来重写 CompareTo 方法。

具体代码如下:

class MyTimerTask implements Comparable<MyTimerTask> {
    private long time;//绝对时间
    private Runnable runnable;//加上 private 体现封装性

    public MyTimerTask(Runnable runnable, long time) {
        this.runnable = runnable;
        this.time = time + System.currentTimeMillis();//绝对时间
    }

    public void run() {//方便后续调用
        runnable.run();
    }
    public long getTime(){
        return time;
    }

    //重写比较器,从小到大排序
    @Override
    public int compareTo(MyTimerTask o) {
        return this.time >= o.time ? 1 : -1;
    }
}

3.2 MyTimer 实现:

这个就是我们要实现的定时器,通过上面在 MyTimerTask 的分析可知,我们这里需要优先级队列来辅助管理任务。同时还需要一个线程来不停的执行队列中的任务,并且还要提供一个 schedule 方法。所以总共要实现的东西有:

• 线程

• 优先级队列(小根堆)

• schedule 方法

• 保证线程安全(通过使用锁的方式要实现)

注意:这里我们不使用 Java 自带的优先级阻塞队列,原因是:优先级阻塞队列本身内部就有一个锁,我们为了保证线程安全,外面还要加一层锁,如果使用阻塞队列,那就是两个锁嵌套的情况,一不小心就会出现死锁的情况,所以倒不如我们同一处理,只使用一个锁即可。在自己实现阻塞队列的时候不能使用 continue 来循环等待(“忙等”),这样很消耗 CPU 资源,也不能使用 sleep 来进行阻塞,因为 sleep 不能释放锁(抱着锁睡),线程睡了就真的睡了,综上我们选择采用 wait 的方式来进行阻塞。

还有一些小细节在代码中都有标注释,这里就不再赘述了。 

具体代码如下:

• 大体框架:

线程一直不停的扫描队首元素,看看是否能执行这个任务。

class MyTimer {
    private Object locker = new Object();
    //不用阻塞优先级队列,因为有两个锁,一不小心就死锁了
    private PriorityQueue<MyTimerTask> heap = new PriorityQueue<MyTimerTask>();//因为有实现 comparable 所以 不用再传入比较器
    public MyTimer(){
        Thread thread = new Thread(() -> {
            try{
                while (true) {
                    synchronized (locker) {
                        if (heap.isEmpty()) {
                            locker.wait();
                        }
                        MyTimerTask tmp = heap.peek();
                        long curTime = System.currentTimeMillis();
                        if (curTime >= tmp.getTime()) {
                            //执行
                            tmp.run();
                            heap.poll();
                        } else {
                            //时间还未到
                            locker.wait(tmp.getTime() - curTime);
                        }
                    }
                }
            }catch(InterruptedException e){//把异常统一处理
                throw new RuntimeException(e);
            }
        });
        thread.start();//线程启动
    }
}

• schedule 方法:

 Timer 类提供的核心方法为 schedule,用于注册一个任务,并指定这个任务多长时间后执行。这里加上锁有两个原因:1. 保证线程安全。2. 唤醒执行队列元素线程(如果在 wait 中的话)。

public void schedule(Runnable runnable,long delay){
        synchronized(locker){
            MyTimerTask task = new MyTimerTask(runnable,delay);
            heap.add(task);
            locker.notify();//这里必须要唤醒一下,因为添加新的任务后,绝对时间最小的不一定就是栈顶元素,要把新加入的元素一起考虑一下。
        }
    }

• 验证正确性:

还是上面在演示标准库 Timer 那里的案例。

public class demo1 {

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

案例演示效果如下:

可以看到符合我们预期的效果

标签:定时器,schedule,JavaEE,Runnable,线程,new,多线程,public
From: https://blog.csdn.net/2301_80035594/article/details/139705223

相关文章

  • 多线程设计模式之Future模式
    在JDK中实现线程同步等待闭环(FutureTask/Future)中已经涉及到了Future模式,相对与多线程设计模式之WorkerThread模式有何异同呢?在多线程设计模式之WorkerThread模式中client和worker之间没有任何直接联系,即worker执行的结果client是不关心的;Future模式与之的差别就在于此线......
  • 学习笔记STMF4 TIMER定时器(使用开发板立创天空星STMF4)
    目录                                                #定时器的介绍             #怎么去理解定时器的预分频系数                                        ......
  • java object多大 java对象内存模型 数组有多长(八)多线程
    在javaobject多大java对象内存模型数组有多长(四)已经访问的对象记录优化中,用byte数组处理,现在它将暴露在多线程中 1对byte数组加volatile2可见性:用Unsafe控制ConcurrentHashMap内并发数组元素的可见性中的方法来byte数组元素的读写 原子性1)compareandsetbyte 2......
  • 封装定时器方法
    需求:查询的历史数据需要定时3分钟刷新(产品提的要求照做!!!)//周期性地执行指定的回调函数,并在组件销毁时清除该定时器,以防止内存泄漏或不必要的回调执行exportconsttimmingLoadingsTime=(callback,time)=>{constrollertimer2=ref(null);rollertimer2.value=se......
  • delphi:利用定时器读取串口返回数据
    定时器20毫秒运行一次,单字符读取,如果读取到就保存到全局变量receData中,否则就输出到文本框中,并重置receData。优点:单字符读取,解决了按长度读取的弊端,如果按长度读取,很多时候并不知道究竟要读取多长,有的时候能读取完整,有的时候只读取了部分。procedureTfrmLC.tmrReceDataTimer(S......
  • 多线程设计模式之Worker Thread模式
    以前用C/C++写进程池,要么一下子fork最大进程数,要么来一个任务fork一个进程。多线程也可以这样设计,并总结这种模式为WorkerThread模式。类图如下:具体实现参考如下代码(一次性开启足够多的线程):1)Request......
  • C#/.Net 中的多线程介绍和最佳实践
    I/引言计算机中的线程CPU调度程序和时间切片进程和线程并发和并行性异步与多线程在C中使用多线程的好处#II线程C语言#线程生命周期创建、启动和暂停线程加入中止中断线程取消:停止线程的更好方法III/线程问题死锁和争用条件使用Join和LocksAutoRese......
  • HarmonyOS_多线程
    并发是指在同一时间段内,能够处理多个任务的能力。为了提升应用的响应速度与帧率,以及防止耗时任务对主线程的干扰,HarmonyOS系统提供了异步并发和多线程并发两种处理策略。异步并发是指异步代码在执行到一定程度后会被暂停,以便在未来某个时间点继续执行,这种情况下,同一时间只有一......
  • 定时器的认识
    目录定时器定时器怎么定时定时器编程定时器控制led一秒亮灭定时器知识点补充定时器简介:C51中的定时器和计数器是同一个硬件电路支持的,通过寄存器配置不同,就可以将他当做定时器或者计数器使用。确切的说,定时器和计数器区别是致使他们背后的计数存储器加1的信号不同......
  • FreeRTOS 体验教程:3.如何用互斥量实现FreeRTOS多线程访问共享资源?
    FreeRTOS互斥量使用教程互斥量(Mutex)是一种特殊的信号量,用于管理对共享资源的访问。在FreeRTOS中,互斥量的句柄类型依然是xSemaphoreHandle。本文将详细介绍如何在FreeRTOS中创建和使用互斥量,并通过实例展示其运行效果。1.创建互斥量在FreeRTOS中,创建互斥量非常简......