• JavaEE多线程知识--计时器



    定时器

    代码中的定时器,通常都是设定"多长时间之后,执行某个动作"

    服务器开发中,客户端请求服务器

    客户端发送请求之后,就需要等待服务器的响应

    客服端不能一直死等下去,如果一直死等,很可能程序就卡死了

    因此客户端经常会设置一个"超时时间",这里就可以使用定时器来实现

    标准库里的定时器~

    在这里插入图片描述
    TimerTask task 是要安排的任务,就是一个Runnable ,需要继承TimeTask,然后重写run,从而指定要执行的任务

    long delay是多长时间之后来执行这个任务,经过delay ms之后执行task任务

    public class DemoTimer {
        public static void main(String[] args) {
            //标准库的定时器
    
            Timer timer = new Timer();
    
    
           timer.schedule(new TimerTask() {
               @Override
               public void run() {
                   System.out.println("欸嘿1");
               }
           },3000);
    
           timer.schedule(new TimerTask() {
               @Override
               public void run() {
                   System.out.println("哇哦2");
               }
           },4000);
    
           timer.schedule(new TimerTask() {
               @Override
               public void run() {
                   System.out.println("吧唧3");
               }
           },5000);
    
            System.out.println("开始计时");
        }
    }
    

    在这里插入图片描述

    执行完上述任务之后,进程没有退出!!!

    Timer内部需要一组线程来执行注册的任务,而这里的线程是前台线程,前台线程会影响进程退出,所以进程没有退出~

    自己实现Timer

    shcedule第一个参数是一个任务

    1️⃣需要能够描述这个任务,任务包含两个方面的信息,一个是要执行啥工作,一个是啥时候执行~~

    //这个类描述一个任务
    class MyTask {
        //要执行的任务
        private Runnable runnable;
        //什么时间执行这个任务
        private long time;
    
        public MyTask(Runnable runnable , long delay) {
            this.runnable = runnable;
            this.time = System.currentTimeMillis()+delay;
        }
    }
    

    2️⃣看下如何让MyTimer管理多个任务
    一个timer是可以安排多个任务的~
    可以使用一个阻塞的优先级队列:

    private BlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
    
    class MyTimer {
        private BlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
    
        public void schedule(Runnable runnable,long after) throws InterruptedException {
            MyTask myTask = new MyTask(runnable,after);
            queue.put(myTask);
        }
    
    
    }
    

    3️⃣任务已经被安排到优先级阻塞对列中了,接下来就需要从队列中取元素了,接下来就需要从队列中取元素,创建一个单独的扫描线程,让这个线程不停的来检查队首元素时间是否到了,如果时间到了,则执行该任务~

     //创建一个扫描线程
            Thread t = new Thread(()->{
                while(true) {
                    //取出队首元素
                    try {
                        MyTask task = queue.take();
                        if(task.getTime() <= System.currentTimeMillis()) {
                            //到点了,可以执行任务了
                            task.getRunnable().run();
                        }else {
                            //时间还没到
                            queue.put(task);
    
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
    

    由于阻塞队列,无法阻塞的取队首元素,因此需要先取出任务,然后才能够判定任务时间是否到了,如果任务时间没到,还需要把任务放回去

    4️⃣运行一下上述代码,我们发现出了一些问题
    在这里插入图片描述
    通过观察异常,可以发现问题出在往阻塞的优先级队列里放MyTask时,没有实现Comparable接口上:

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

    关于这里this.timeo.time的顺序我建议大家试一下更直观,背的话很容易出错~

    但是上述代码还有很严重的问题~~
    首先是忙等问题!!
    在这里插入图片描述
    上面红框的代码是一直在执行的!!
    也就是说这个循环一直在忙等!!!
    CPU并没有被空闲出来!
    这里的等待其实是没什么意义的!

    5️⃣使用wait就可以解决上述问题~~
    在这里插入图片描述

    在循环后就立刻加锁就可以避免notifytakewait之间执行了,扫描线程会先拿到锁,然后take,然后执行中间逻辑,一直到wait,在这个过程中,schedule线程会阻塞等待锁,知道扫描线程执行了wait时,扫描线程释放了锁,schedule线程就拿到了锁,进行了通知!
    这个时候wait就立即被唤醒了!!接下来继续重新取队首元素~
    就可以执行新的更早的任务了~

    6️⃣到这里自己实现Timer就结束了~

    给大家贴上代码:

    import java.util.PriorityQueue;
    import java.util.TimerTask;
    import java.util.concurrent.BlockingDeque;
    import java.util.concurrent.PriorityBlockingQueue;
    
    /**
     * @Author YuanYuan
     * @Date 2022/9/21
     * @Time 15:18
     */
    //这个类描述一个任务
    class MyTask implements Comparable<MyTask>{
        //要执行的任务
        private Runnable runnable;
        //什么时间执行这个任务
        private long time;
    
        public MyTask(Runnable runnable , long delay) {
            this.runnable = runnable;
            this.time = System.currentTimeMillis()+delay;
        }
    
        public Runnable getRunnable() {
            return runnable;
        }
    
        public void setRunnable(Runnable runnable) {
            this.runnable = runnable;
        }
    
        public long getTime() {
            return time;
        }
    
        public void setTime(long time) {
            this.time = time;
        }
    
        @Override
        public int compareTo(MyTask o) {
            return (int) (this.time - o.time);
        }
    }
    class MyTimer {
        private BlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
    
        private Object locker = new Object();
    
        public MyTimer() {
            //创建一个扫描线程
            Thread t = new Thread(()->{
                while(true) {
                    //取出队首元素
                    try {
                        synchronized (locker) {
                            MyTask task = queue.take();
                            long curTime = System.currentTimeMillis();
                            if(curTime >= task.getTime()) {
                                //到点了,可以执行任务了
                                task.getRunnable().run();
                            }else {
                                //时间还没到
                                queue.put(task);
                                //没到时间,就等待
                                locker.wait(task.getTime() - curTime);
                            }
                        }
    
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            t.start();
        }
    
        public void schedule(Runnable runnable,long after) throws InterruptedException {
            MyTask myTask = new MyTask(runnable,after);
            queue.put(myTask);
            synchronized (locker) {
                locker.notify();
            }
        }
    
    
    }
    public class TestDemo2 {
        public static void main(String[] args) throws InterruptedException {
            MyTimer timer = new MyTimer();
            timer.schedule(new Runnable() {
                @Override
                public void run() {
                    System.out.println("欸嘿11");
                }
            },3000);
    
            timer.schedule(new Runnable() {
                @Override
                public void run() {
                    System.out.println("哇哦22");
                }
            },4000);
    
            timer.schedule(new Runnable() {
                @Override
                public void run() {
                    System.out.println("吧唧33");
                }
            },5000);
            System.out.println("开始计时");
        }
    }
    
    

    总结

    在这里插入图片描述

    你可以叫我哒哒呀
    本篇到此结束
    “莫愁千里路,自有到来风。”
    我们顶峰相见!
  • 相关阅读:
    考研王道强化阶段(二轮复习)“算法题”备考打卡表 记录
    仿everything的文件搜索工具项目详解:Part1
    MongoDB部署
    VS2019,安装完成,但出现警告,无法安装sql sys clr types
    candence画环形贴片焊盘
    浅谈图片宽度自适应解决方案
    差分,前缀和,离散化——模板
    LiteFlow 开源编排规则引擎
    第三十一章 使用 CSP 进行基于标签的开发 - 转义和引用HTTP输出
    API网关之网关概述、技术选型
  • 原文地址:https://blog.csdn.net/m0_58437435/article/details/126978534