• 【多线程案例】定时器应用及实现


    1. 定时器是什么?

    定时器就类似生活中的闹钟,它是软件开发中的一个重要组件。当有些线程我们并不希望它立刻执行,这个时候我们就可以使用定时器,规定线程在多长时间后执行。
    定时器

    2. 定时器的应用

    定时器在Java标准库中提供的有一个Timer类。Timer中核心的方法是schedule,schedule中有俩个参数,一个是参数是执行任务代码,另一个参数是指定代码多少毫秒后执行。

    public class Test {
        public static void main(String[] args) {
            Timer timer = new Timer();
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    System.out.println("3秒!");
                }
            },3000);
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    System.out.println("2秒");
                }
            },2000);
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    System.out.println("1秒");
                }
            },1000);
            System.out.println("开始");
    
        }
    }
    
    • 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

    运行结果:
    111

    3. 自己实现定时器

    自己实现一个定时器能够帮助我们更好的理解定时器。实现定时器之前我们要知道定时器的构成。

    1. 一个带优先级的堵塞队列;(后面说为什么使用优先级堵塞队列)
    2. 队列中元素都是一个个要执行的任务;
    3. 队列属性要带有时间属性;
    4. 同时有个线程不停扫描队列元素,找到要执行的任务。

    为什么使用优先级堵塞队列?
    如果使用普通队列:在执行任务前,扫描线程不停的遍历队列,找到要执行的任务,在遍历时,线程执行的速度时非常快的,这个时候就非常浪费CPU资源。
    使用优先级堵塞队列:那么,我们的扫描线程只需要扫描队列的队头,因为 在这个优先级堵塞队列中可以使最先执行的任务放在队头,而且在扫描到这个对头后,我们也可以进入休眠,更一步减少资源浪费。

    class MyTimerTask implements Comparable<MyTimerTask>{
        //TimerTask的两个参数
        private Runnable runnable;
        private long time;
        //实例化
        public MyTimerTask(Runnable runnable, long time){
            this.runnable = runnable;
            //计算要执行的时间
            this.time = System.currentTimeMillis() + time;
    
        }
    
        public Runnable getRunnable() {
            return runnable;
        }
    
        public long getTime() {
            return time;
        }
        //比较
    
        @Override
        public int compareTo(MyTimerTask o) {
            return (int) (time - o.time);
        }
    }
    class MyTimer{
        //带优先级的堵塞队列
        PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
        //锁
        Object locker = new Object();
        //实现Timer类中的schedule的方法,接收任务和时间两个参数
        public void schedule(Runnable runnable, long time){
            //多线程环境,对队列操作都加锁,保证线程安全
            synchronized (locker) {
                //任务时间
                MyTimerTask myTimerTask = new MyTimerTask(runnable, time);
                //加入到队列
                queue.offer(myTimerTask);
                //每次添加元素,都唤醒一次循环队列,防止超时
                locker.notify();
            }
        }
        public MyTimer(){
            //在实例化一个MyTimer,那么就就会启动这个扫描线程,进行扫描
            Thread t = new Thread(() -> {
                //使用循环,扫描线程并不是只执行一次
                while (true) {
                    try {
                        //加锁,保证线程安全
                        synchronized (locker) {
                            //使用while,不要使用if,防止出现非线程安全
                            while (queue.isEmpty()) {
                                //队列为空,没有要执行的任务,进行等待,堵塞
                                locker.wait();
                            }
                            //取到队列对头,但不出队列,并不一定到时间执行
                            MyTimerTask myTimerTask = queue.peek();
                            //现在的时间
                            long time = System.currentTimeMillis();
                            //现在的时间如果大于任务要执行的时间,队头便出队列
                            if (time >= myTimerTask.getTime()) {
                                queue.poll();
                                //执行任务
                                myTimerTask.getRunnable().run();
                            } else {
                                //如果不到时间,进行等待,计算时间差,防止还没到就被唤醒,带来资源浪费
                                locker.wait(myTimerTask.getTime() - time);
                            }
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            //启动!!!
            t.start();
        }
    }
    public class Test3 {
        public static void main(String[] args) {
            MyTimer myTimer = new MyTimer();
            myTimer.schedule(new Runnable() {
                @Override
                public void run() {
                    System.out.println("3秒!");
                }
            },3000);
            myTimer.schedule(new Runnable() {
                @Override
                public void run() {
                    System.out.println("2秒");
                }
            },2000);
            myTimer.schedule(new Runnable() {
                @Override
                public void run() {
                    System.out.println("1秒");
                }
            },1000);
            System.out.println("开始!");
        }
    }
    
    • 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
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103

    运行结果:
    333

  • 相关阅读:
    音视频技术开发周刊 | 318
    五金机电行业S2B2B商城系统打破传统线下营销方式,实现企业高质量发展
    软件测试面试题之自动化测试题合集(金九银十必备)
    Linux-组管理和权限管理
    FFmpeg入门详解之123:代码封装:摄像头h264/5编码并存储
    【Unity】关于升级到2021.3.12之后URP编译错误的问题
    【Linux】冯诺依曼体系结构、操作系统及进程概念
    Spring启动后进行一些初始化的方式汇总
    干货 | RDBMS 索引类型概述
    卡尔曼时间序列预测
  • 原文地址:https://blog.csdn.net/weixin_73392477/article/details/132654789