多线程(八)【定时器】

各位小伙伴们,上篇文章我为大家归纳总结了关于线程池的概念,标准库中线程池的构造方法,并且对每个构造对象单独领出来了解了一下,然后使用Java为我们封装好的线程池的使用,最后我们自己模拟实现了一个固定核心线程数的线程池~~~

这边文章,我将继续归纳总结在多线程中的最后一个案例 ---- 定时器

在了解定时器之前,我们也务必捋顺一下之前我们针对多线程编写讨论的其他案例:

1.)单例模式

a.)饿汉模式

b.)懒汉模式

2.)阻塞队列

3.)线程池

什么是定时器

当我们客户端像服务器发送一个请求,客户端需要等待服务端的相应~~

但是客户端也不是一直平白无故的等待,它有一定固定的等待时间,如果超过这个等待的时间,服务器还是没有发送相应,客户端就请求失败!!!这个等待就需要定时器来计算等待时间~~

我们有时候在实际开发中,也希望一些程序在一定时间后才执行,这个时候我们程序员就可以使用定时器来帮助我们。

使用标准库中的 Timer 类

在Java标准库中,大佬们为我们封装实现了定时器类(Timer),我们可以指定一个时间后执行代码任务,然后Timer类内部会有线程帮我们去执行任务,并且内部专门的线程是前台线程。

java 复制代码
public class Demo {
public static void main(String[] args) {
    //使用Timer类
    Timer timer = new Timer();


// 3000ms后执行这个任务
    timer.schedule(new TimerTask() {
        //因为TimerTask内部使用Runnable接口,所以需要重写Runnable中的run方法
        public void run() {
            System.out.println("hello 3000ms~~~");
        }
    }, 3000);

// 2000ms后执行这个任务
    timer.schedule(new TimerTask() {
        public void run() {
            System.out.println("hello 2000ms~~~");
        }
    }, 2000);


// 1000ms后执行这个任务
    timer.schedule(new TimerTask() {
        public void run() {
            System.out.println("hello 1000ms~~~");
        }
    });

    Thread.sleep(4000);

//因为Timer内部的线程是前台线程,所以需要我们手动关闭前台线程
    timer.cancel();

 }
}

手动模拟实现定时器

使用PirorityQueue ---- 因为我们需要让最早执行的任务在堆顶,就可以直接拿到任务

注意这里面关于synchronized的使用

在代码中,我们首先要分辨 time 和delay:

time:比如现在11:00点,妈妈叫我看着锅,5分钟后关掉煤气,所以time = 11:05

delay:就是妈妈叫我5分钟后关掉煤气,所以5分钟后就是delay

为什么不适用ArrayList???

因为我们要知道哪个任务率先被执行,就需要遍历整个ArrayList,而且我们添加任务的时间是随机的,每次都是从最后一个元素开始添加,所以要判断某个任务执行时间是否到了,就需要遍历ArrayList,就更消耗时间了,还可能出现误差~~~

为什么不适用TreeMap???

因为TreeMap无法拿到第一个元素

java 复制代码
//实现执行Timer任务的任务类
class MyTimerTask implements Copmarable<MyTimerTask> {
    private Runnable runnable;
    private long time;//保存什么时刻执行该任务。ms级别的时间戳

    public MyTimerTask(Runnable runnable, long delay) {
        this.runnable = runnable;
        this.time = System.currentTimeMillis() + delay;
    }

// 获取当前任务执行的时刻
    public long getTime() {
        return this.time;
    }

// 执行任务
    public void run() {
        runnable.run();//让Runnable的对象去执行任务       
    }

    //重写CompareTo --- 规定比较方法 ---- 对排序方法
    public int CompareTo(MyTimerTask o) {
        return (int)()(this.time - o.thime);
    }

}



//模拟实现Timer类
class MyTimer {

    //使用集合类的堆来存放任务数据
    public PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();

    Object locker = new Object();

    public MyTimer() {
        //创建线程
        Thread t = new Thread(() -> {
            try{
//在while死循环中不断从堆获取任务
                while(true) {
//记得加synchronized,防止线程被调度产生线程安全问题~~~
                    synchronized(locker) {
                        //从堆中取出元素
                        MyTimerTask task = queue.peek();//查看堆顶任务
                        //判断堆顶元素是否为空
                        while(task == null) {
                            locker.wait();//等待任务添加到堆中
                            task = queue.peek();//重新获取堆顶元素
                        }

                        //获取当前的时间,然后判断是否到达执行的时间
                        long currentTime = System.currentTimeMillis();
                        if(currentTime >= task.getTime()) {
                            task.run();//执行任务
                            queue.poll();//从堆顶拿走,让下一个任务成为堆顶
                        }else {
                            //等待到执行的时间才执行,如果有新的任务进来,也可以唤醒
                            //避免有更早时间的任务进来被忽略
                            locker.wait(task.getTime() - currentTime);
                        }
                    }
                }
            }catch(InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        t.start();
    }




    //提供schedule方法
//  这里的delay是在写方法的时候想要多少秒后才执行此任务!!!
    public void schedule(Runnable task, long delay) {
        synchronized(locker) {
            MyTimeTask task = new MyTiemrTask(runnable, delay);
            //将任务加入堆中
            queue.add(task);
            locker.notify();//往堆中加入任务,并且唤醒wait
        }
    }

}



public class Demo {
public static void main(String[] args) {

    MyTimer timer = new MyTimer();

// 3000ms后执行此任务
    timer.schedule(() -> {
        System.out.println("hello 3000ms");
    }, 3000);

// 2000ms后执行此任务
    timer.schedule(() -> {
       System.out.println("hello 2000");
    }, 2000);

// 1000ms后执行此任务
    timer.schedule(() -> {
        System.out.println("hello 1000");
    }, 1000);

 }
}
相关推荐
NotStrandedYet2 小时前
《国产系统运维笔记》第2期:在 openEuler 24.03 LTS 上在线部署 Tomcat 9 全记录
java·tomcat·信创·国产化·openeuler·信创运维·国产化运维
月明长歌2 小时前
Selenium Web 自动化测试脚本总结
java·selenium·测试工具
多看书少吃饭2 小时前
文件预览的正确做法:从第三方依赖到企业级自建方案(Vue + Java 实战)
java·前端·vue.js
高山上有一只小老虎2 小时前
JPA实现分页查询
java·spring boot·后端
阿蒙Amon2 小时前
C#每日面试题-Task和ValueTask区别
java·开发语言·c#
FAFU_kyp2 小时前
Rust 泛型(Generics)学习教程
开发语言·学习·rust
Java程序员威哥2 小时前
【包教包会】SpringBoot依赖Jar指定位置打包:配置+原理+避坑全解析
java·开发语言·spring boot·后端·python·微服务·jar
a程序小傲2 小时前
中国邮政Java面试被问:边缘计算的数据同步和计算卸载
java·服务器·开发语言·算法·面试·职场和发展·边缘计算