多线程新手村4--定时器

定时器是日常开发中很常见的组件,定时器大家可能不知道是干什么的,但是定时炸弹肯定都听过,定个时间,过一段时间后bomb!!!爆炸

定时器的逻辑和这个一样,约定一个时间,这个时间到达之后,执行某个代码逻辑;定时器的常见场景有网络通信,定时邮件发送等等。

计算机网络中的"超时重传"就用到了定时器。当客户端向服务器发送消息时,服务器可能由于某些问题一直不回复,此时该怎么办呢?肯定不能无限的等,需要有一个最大的期限,当到达这个最大期限时,该放弃呢?还是重传呢?或者想别的解决办法,这时就用到了定时器。

内部库Timer

当然,不光要学会怎么使用内部库提供的定时器,我们还要自己手写一个定时器出来。

怎么写呢?

1、需要一个线程,不断扫描是否有任务到达时间,可以执行了。

2、需要一个数据结构,存储所有的任务。

3、还需要创建一个类,通过类的对象来描述一个任务(至少要包含做什么和时间)。

那么又出现一个问题,该使用什么数据结构呢?

用数组吗?不行,用数组每次扫描都要遍历所有任务,时间开销太大;

想想我们学过的数据结构,每次执行时间最小的,是的,没错,就是它,它就是--优先级队列!

优先级队列每次放入元素时都会更新顺序,保证时间最小的一定在最前面,因为我们每次可以执行的一定是时间最小的,之后的元素都不需要搜索,所以时间复杂度是O(1)。

代码如下:

java 复制代码
package Thread;

import java.util.PriorityQueue;
import java.util.Timer;
import java.util.TimerTask;

class MyTimerTask implements Comparable<MyTimerTask> {
    private Runnable runnable;
    //要有一个要执行的任务
    private long time;
    //还要有一个执行任务的时间(这里是绝对时间)

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

    @Override
    public int compareTo(MyTimerTask o){
        return (int)(this.time - o.time);
        //这样的写法,就是让队首元素是最小时间的值
    }

    public long getTime(){
        return time;
    }

    public Runnable getRunnable(){
        return runnable;
    }
}

class MyTimer{
    private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
    private Object locker = new Object();

    public void schedule(Runnable runnable,long delay){
        synchronized (locker){
            queue.offer(new MyTimerTask(runnable,delay));
            locker.notify();
        }
    }

    public MyTimer(){
        Thread t = new Thread(() -> {
            while(true){
                try{
                    synchronized (locker){
                        while(queue.isEmpty()){
                            locker.wait();
                        }
                        MyTimerTask task = queue.peek();
                        long curTime = System.currentTimeMillis();
                        if(curTime >= task.getTime()){
                            task.getRunnable().run();
                            queue.poll();
                        }
                        else{
                            locker.wait(task.getTime() - curTime);
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
    }
}


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

        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("2000");
            }
        },2000);

        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("1000");
            }
        },1000);

        System.out.println("程序开始执行");
    }
}

这里为什么要用wait呢?用sleep可以吗?

答案是不可以!

当我们向队列中插入元素时,会调用notify方法,这里使用wait是为了当新插入队列中的元素的时间比当前队头的元素的时间小时,就需要进行更新,重新判定一下最早的任务以及此处的等待时间。

相关推荐
bingHHB2 分钟前
金蝶云星空旗舰版 × 赛狐ERP:亚马逊卖家业财一体化的最后一公里
运维·数据库·集成学习
不早睡不改名@20 分钟前
Netty源码分析---Reactor线程模型深度解析(二)
java·网络·笔记·学习·netty
ji_shuke23 分钟前
CloudFront 跨域问题(CORS)的几种解决方式
服务器·cloudfront
子非鱼@Itfuture32 分钟前
`<T> T execute(...)` 泛型方法 VS `TaskExecutor<T>` 泛型接口对比分析
java·开发语言
2601_9498161632 分钟前
spring.profiles.active和spring.profiles.include的使用及区别说明
java·后端·spring
IMPYLH34 分钟前
Linux 的 install 命令
linux·运维·服务器·bash
寻道模式36 分钟前
【运维心得】“龙虾”非本地访问的坑
运维·服务器
疯狂成瘾者43 分钟前
接口规范设计:返回体 + 错误码 + 异常处理
java·状态模式
阿Y加油吧1 小时前
LeetCode 二叉搜索树双神题通关!有序数组转平衡 BST + 验证 BST,小白递归一把梭
java·算法·leetcode
项目帮1 小时前
Java毕设选题推荐:基于springboot区块链的电子病历数据共享平台设计与实现【附源码、mysql、文档、调试+代码讲解+全bao等】
java·spring boot·课程设计