「JavaEE」多线程案例分析2:实现定时器

🎇个人主页Ice_Sugar_7
🎇所属专栏JavaEE
🎇欢迎点赞收藏加关注哦!

🍉简介

定时器类似一个闹钟,时间到了之后就会执行相应的任务
Java 标准库中已经实现了一个定时器的类 Timer

Timer timer = new Timer();

在定义好 timer 之后可以调用 schedule 把一个或多个任务(TimerTask)添加到定时器中

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

第一个参数就是任务内容,每个任务后面都会带有一个时间(第二个参数),这个时间是“相对时间”,是以 schedule 时的时间为基准,过了相对时间后才执行
比如 2000ms,它表示调用 schedule 后再过 2000ms 就会执行这个任务

TimerTask 里面有一个 run 方法,而 run 是线程的入口,说明 timer 创建了一个线程来执行任务。这个线程是前台线程,它会阻止主线程结束,需要我们使用 cancel 主动结束,否则 Timer 不知道其他地方是否会继续添加任务

public static void main(String[] args) {
    Timer timer = new Timer();
    timer.schedule(new TimerTask() {
        @Override
        public void run() {
            System.out.println("2000 ms");
            timer.cancel(); //结束线程
        }
    },2000);
    timer.schedule(new TimerTask() {
        @Override
        public void run() {
            System.out.println("1000 ms");
        }
    },1000);
}

在这里插入图片描述


🍉模拟实现定时器

首先要有一个数据结构负责保存 schedule 的任务(相当于任务清单),因为我们是先执行时间近的任务(比如有两个任务,一个是两点执行,另一个是两点半执行,肯定要先完成前者),换而言之,任务之间是有优先级的,所以要用优先级队列
标准库中提供了 PriorityQueue 和 PriorityBlockingQueue,前者是线程不安全的,后者是线程安全的,在此处的场景中 PriorityBlockingQueue 不太好控制,容易出问题,所以我们用前者

(补充:TreeSet 和 TreeMap 虽然也是有序的,但是获取到最小值的时间复杂度为 O(logN),不及 O(1) 的优先级队列)

然后需要有一个线程不断扫描优先级队列的队首元素,看它时间到了没

public class MyTimer {
    private Thread t;
    private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
    private Object locker = new Object();
    public MyTimer(){
        //定时器构造方法的主体就是启动线程,让它去扫描队首元素
        t = new Thread(() -> {
            while (true) {
                synchronized (locker) {
                    while (queue.isEmpty()) {
                        try {
                            locker.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    MyTimerTask task = queue.peek();
                    long curTime = System.currentTimeMillis();
                    if (curTime >= task.getTime()) { //时间到了,执行任务
                        task.run();
                        queue.poll(); //记得执行后把它出队列
                    } else {
                        try {
                            locker.wait(task.getTime() - curTime); //如果还没到执行时间,那就等待,不要一直循环下去
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
        });
        t.start();
    }

    public void schedule(Runnable runnable,long delay) { //把任务添加到 queue 里面
        synchronized (locker) {
            MyTimerTask task = new MyTimerTask(runnable, delay);
            queue.offer(task);
            locker.notify(); //添加元素后,就可以唤醒处于 wait 状态的线程
        }
    }
}

队列的元素——任务,它是一个类。它的成员变量应该包括时间、能让它跑起来的 Runnable 接口

public class MyTimerTask{
    private long time; //执行任务的时间(注意这个是“绝对时间”)
    private Runnable runnable; //持有 Runnable 接口可以调用它的 run 方法,也可以不持有 Runnable,而是实现 Runnable 接口并重写 run 方法

    MyTimerTask(Runnable runnable,long time) {
        this.runnable = runnable;
        this.time = time + System.currentTimeMillis(); //什么时候执行任务:现在的时间 + 相对时间
    }

    public void run() {
        runnable.run();
    }
}

因为优先级队列要求元素是可排序的,所以我们需要实现 Comparable 接口并重写 compareTo 方法

public class MyTimerTask implements Comparable<MyTimerTask>{
    private long time; //执行任务的时间(注意这个是“绝对时间”)
    private Runnable runnable; //持有 Runnable 接口可以调用它的 run 方法,也可以不持有 Runnable,而是实现 Runnable 接口并重写 run 方法

    MyTimerTask(Runnable runnable,long time) {
        this.runnable = runnable;
        this.time = time + System.currentTimeMillis(); //什么时候执行任务:现在的时间 + 相对时间
    }

    public void run() {
        runnable.run();
    }

    @Override
    public int compareTo(MyTimerTask o) {
        return (int) (o.time-this.time); //时间小的优先级更高
    }
}

补充:compareTo 方法里面是 o.time-this.time 还是 this.time - o.time,不用去刻意记忆,两种都试一下就 ok 了

测试一下:

public class TestDemo {
    public static void main(String[] args) throws InterruptedException {
        MyTimer myTimer = new MyTimer();
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("3000 ms");
            }
        },3000);
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("1000 ms");
            }
        },1000);
    }
}

在这里插入图片描述

  • 21
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值