• JavaEE初阶——多线程(七)——定时器


    在这里插入图片描述

    T04BF

    👋专栏: 算法|JAVA|MySQL|C语言

    🫵 小比特 大梦想

    此篇文章与大家分享多线程的第七篇文章——关于定时器
    如果有不足的或者错误的请您指出!

    4.定时器

    所谓定时器就是类似于闹钟效果,指定一个任务给他,这个任务不会立即执行.而是到达指定的时间后才执行
    定时器在实际开发中非常重要,甚至会单独封装成一个服务器,给整个分布式系统使用

    4.1标准库提供的定时器

    在这里插入图片描述
    这里的TimeTask实际上是继承了Runnable接口的
    在这里插入图片描述
    同时,Timer内部包含的也是前台线程,阻止了进程结束

    4.2自己实现一个定时器

    需求:能够延迟执行任务 ,能够管理多个任务

    需要有:定义一个类.表示一个任务

    通过一定的数据结构来保存多个任务

    还需要有一个线程,来负责执行这里的任务(在指定之间内去执行)

    4.2.1任务类
    public class MyTimeTask {
       /*
       当前自己定义的任务里面要保存执行任务的绝对的时间(时间戳)
       为了后续线程执行的时候,可以方便的判定,该任务是否执行
       */
        private long time;
        
        private Runnable runnable;
        public MyTimeTask(Runnable runnable ,long delay) {
            this.time = System.currentTimeMillis() + delay;//手动换算时间
            this.runnable = runnable;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    4.2.2Timer类
    public class MyTimer {
        /*
    这里实际上最直观的是使用List
    但是如果这里元素很多,就需要不停循环的扫描这里的任务,分别判定是否要执行
    那么我们就可以将这些任务通过优先级队列保存起来,按照时间作为优先级队列的先后标准,就可以做到,队首元素就是最先要执行的任务,那么线程扫描的时候直接判断队首元素即可
    那么我们就要对任务类实现comparable接口
         */
        PriorityQueue<MyTimeTask> queue = new PriorityQueue<>();
        
        
        public void schedule(Runnable runnable,int delay) {
            MyTimeTask timeTask = new MyTimeTask(runnable,delay);
            queue.offer(timeTask);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    此时别忘了对MyTimeTask类实现Comparable接口

    public class MyTimeTask implements Comparable<MyTimeTask>{
       /*
       当前自己定义的任务里面要保存执行任务的绝对的时间(时间戳)
       为了后续线程执行的时候,可以方便的判定,该任务是否执行
       */
        private long time;
    
        private Runnable runnable;
        public MyTimeTask(Runnable runnable ,long delay) {
            this.time = System.currentTimeMillis() + delay;//手动换算时间
            this.runnable = runnable;
        }
    
    
        @Override
        public int compareTo(MyTimeTask o) {
            return (int)(this.time - o.time);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    4.2.3 有一个线程来负责执行这里的任务
    public class MyTimer {
        /*
    这里实际上最直观的是使用List
    但是如果这里元素很多,就需要不停循环的扫描这里的任务,分别判定是否要执行
    那么我们就可以将这些任务通过优先级队列保存起来,按照时间作为优先级队列的先后标准,就可以做到,队首元素就是最先要执行的任务,那么线程扫描的时候直接判断队首元素即可
    那么我们就要对任务类实现comparable接口
         */
        PriorityQueue<MyTimeTask> queue = new PriorityQueue<>();
    
        public MyTimer () {
            Thread t = new Thread (() -> {
               while(true) {
                   //此时就要判断当前优先级任务队列的头任务是否到达执行时间了
                   //如果到达则执行,不到达则循环判断
               }
            });
        }
        public void schedule(Runnable runnable,int delay) {
            MyTimeTask timeTask = new MyTimeTask(runnable,delay);
            queue.offer(timeTask);
        }
    
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    public class MyTimeTask implements Comparable<MyTimeTask>{
       /*
       当前自己定义的任务里面要保存执行任务的绝对的时间(时间戳)
       为了后续线程执行的时候,可以方便的判定,该任务是否执行
       */
        private long time;
    
        private Runnable runnable;
        public MyTimeTask(Runnable runnable ,long delay) {
            this.time = System.currentTimeMillis() + delay;//手动换算时间
            this.runnable = runnable;
        }
        
        public void run() {
            this.runnable.run();
        }
        public long getTime() {
            return time;
        }
    
        @Override
        public int compareTo(MyTimeTask o) {
            return (int)(this.time - o.time);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    那么我们就来具体实现schedule内的细节

        public MyTimer () {
            Thread t = new Thread (() -> {
               while(true) {
                   //此时就要判断当前优先级任务队列的头任务是否到达执行时间了
                   //如果到达则执行,不到达则循环判断
                   if(queue.isEmpty()) {
                       continue;
                   }
                   long curTime = System.currentTimeMillis();
                   MyTimeTask task = queue.peek();
                   if(task.getTime() <= curTime) {
                       task.run();
                       queue.poll();
                   }else{
                       //时间未到
                       continue;
                   }
               }
            });
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    但是此时可能存在不同线程同时修改同一个队列的情况,就要加入锁

    public class MyTimer {
        /*
    这里实际上最直观的是使用List
    但是如果这里元素很多,就需要不停循环的扫描这里的任务,分别判定是否要执行
    那么我们就可以将这些任务通过优先级队列保存起来,按照时间作为优先级队列的先后标准,就可以做到,队首元素就是最先要执行的任务,那么线程扫描的时候直接判断队首元素即可
    那么我们就要对任务类实现comparable接口
         */
        PriorityQueue<MyTimeTask> queue = new PriorityQueue<>();
        private Object locker = new Object();
        public MyTimer () {
            Thread t = new Thread (() -> {
                synchronized (locker) {
                    while(true) {
                        //此时就要判断当前优先级任务队列的头任务是否到达执行时间了
                        //如果到达则执行,不到达则循环判断
                        if(queue.isEmpty()) {
                            continue;
                        }
                        long curTime = System.currentTimeMillis();
                        MyTimeTask task = queue.peek();
                        if(task.getTime() <= curTime) {
                            task.run();
                            queue.poll();
                        }else{
                            //时间未到
                            continue;
                        }
                    }
                }
            });
        }
        public void schedule(Runnable runnable,int delay) {
            synchronized (locker) {
                MyTimeTask timeTask = new MyTimeTask(runnable,delay);
                queue.offer(timeTask);
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    此时还存在两个比较核心的问题:

    (1)上述的循环等待,实际上这个代码逻辑处于"忙等"的状态,确实是在等,但是等的过程中很忙,比如14:00要执行任务,但是13:00就开始等了

    上述代码在短时间内疚会循环很多次,上述操作都是在"空转",一直在消耗cpu,没有真正执行任务

    而我们要实现的就是在等待的时间内,要释放cpu资源

    public class MyTimer {
        /*
    这里实际上最直观的是使用List
    但是如果这里元素很多,就需要不停循环的扫描这里的任务,分别判定是否要执行
    那么我们就可以将这些任务通过优先级队列保存起来,按照时间作为优先级队列的先后标准,就可以做到,队首元素就是最先要执行的任务,那么线程扫描的时候直接判断队首元素即可
    那么我们就要对任务类实现comparable接口
         */
        PriorityQueue<MyTimeTask> queue = new PriorityQueue<>();
        private Object locker = new Object();
        public MyTimer () {
            Thread t = new Thread (() -> {
                try {
                    while(true) {
                        synchronized (locker) {
                            //此时就要判断当前优先级任务队列的头任务是否到达执行时间了
                            //如果到达则执行,不到达则循环判断
                            if(queue.isEmpty()) {
                                locker.wait();//队列为空,等待,直到有新的任务进来
                            }
                            long curTime = System.currentTimeMillis();
                            MyTimeTask task = queue.peek();
                            if(task.getTime() <= curTime) {
                                task.run();
                                queue.poll();
                            }else{
                                locker.wait(task.getTime() - curTime);
                                //时间未到,等待,直到有新的任务进来(判断新的任务是否要执行)
                                //或者时间到了,执行
                            }
                        }
                    }
                }catch(InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        public void schedule(Runnable runnable,int delay) {
            synchronized (locker) {
                MyTimeTask timeTask = new MyTimeTask(runnable,delay);
                queue.offer(timeTask);
                locker.notify();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    为什么这里不使用PriorityBlockingQueue呢??

    实际上不如手动加锁,因为引入阻塞队列只能解决队列为空的阻塞,而时间没到的阻塞还是要我们自己去实现,还但是要引入新的锁,代码就搞复杂了,并且阻塞队列里面本来就有一把锁,这样反而可能导致死锁的出现

    这里的wait能不能换成sleep?? ----不行!!!

    notift唤醒wait,属于常规手段,是我们处理正常业务的流程,但是sleep通过interrupt唤醒,是处理异常业务的

    此外,更加致命的是,wait休眠期间会释放锁,但是sleep可不会释放锁

    感谢您的访问!!期待您的关注!!!

    在这里插入图片描述

    T04BF

    🫵 小比特 大梦想
  • 相关阅读:
    欧拉计划Python解法(第6题-第10题)
    系统提示mfc100u.dll丢失或错误的解决方法分享
    【C语言】求解数独 求数独的解的个数 多解数独算法
    docker下快速部署openldap与self-service-password
    C# 图片的绘制
    day4 学习python爬虫——接口与常见反爬
    【计算机网络-自顶向下方法】应用层(SMTP、POP3、DNS)
    【三维目标检测】3DSSD(二)
    数据结构与算法------二叉树
    CSS基础(10)- 常规流
  • 原文地址:https://blog.csdn.net/m0_60963435/article/details/138058824