多线程案例3:定时器

多线程系列文章目录


在这里插入图片描述

💥 💥 💥如果你觉得我的文章有帮助到你,还请【关注➕点赞➕收藏】,得到你们支持就是我最大的动力!!!
💥 💥 💥

版权声明:本文由【马上回来了】原创、在CSDN首发、需要转载请联系博主。
版权声明:本文为CSDN博主「马上回来了」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

在这里插入图片描述

🚀🚀🚀 新的知识开始喽🚀🚀🚀
在这里插入图片描述


1.标准库里的定时器

标准库里提供了Timer类,Timer的核心方法是schedule,schedule有两个参数,第一个参数是指定将要执行的任务代码,第二个参数是指定多长时间后执行,单位为毫秒.

public class Time {
    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {//TimerTask就是一个Runnable接口,重写run方法,改线程要执行的任务
            @Override
            public void run() {
                System.out.println("时间到了!!!快起床!!!");
            }
        },3000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("1");
            }
        },4000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("2");
            }
        },5000);
        System.out.println("开始计时");

    }
}

2.定时器的模拟实现

定时器的构成:
1.带优先级的阻塞队列:时间靠前的先执行
2.队列里的每个元素都是一个Task对象
3.Task对象包括要执行的任务和执行的时间
4.同时有个worker线程一直扫描队首元素,看队首元素是否要执行
在这里插入图片描述
完整代码:

class Task implements Comparable<Task>{//Task得放入到带有优先级的阻塞队列里,因此必须可比较的,实现Comparable接口
    //得有两个成员变量 1.指定执行的任务 2.delay执行的时间 转化为时间戳
    Runnable runnable;
    long time;
    public Task(Runnable runnable,long delay){
        this.runnable = runnable;
        this.time = System.currentTimeMillis()+delay;
    }

    @Override
    public int compareTo(Task o) {
        return (int)(time - o.time);
    }
}
class MyTMR{
    BlockingQueue<Task> queue = new PriorityBlockingQueue<>();
    public void schedule(Runnable runnable,long delay) throws InterruptedException {
        //当添加一个任务时,就得创建一个Task对象,并且将task放入到优先级阻塞队里里
       Task task = new Task(runnable,delay);
           queue.put(task);
       synchronized (this){
           notify();//唤醒else里的wait
       }

    }
    //在优先级阻塞队列里有了task之后,worker扫描线程就可以持续扫描优先级阻塞队列了
    public MyTMR(){
        Thread t = new Thread(){
            @Override
            public void run() {
                synchronized (this){
                    while(true){
                        try {

                            //取出队首元素
                            Task task = queue.take();
                            //判断队首元素的任务是否要执行
                    /*
                    当前时间大于或等于最初设定的时间就得执行任务了
                     */
                            if(System.currentTimeMillis()>=task.time){
                                task.runnable.run();
                            }else{//还未到达时间,将队首元素放回到队里,进行等待
                                queue.put(task);
                                //等待的时间:
                                wait(task.time - System.currentTimeMillis());
                                //如果有队列里有新的任务,queue.put后会唤醒wait,重新扫描优先级阻塞队列里的队首元素,如果有更早要执行的任务,那么等待的时间就会更新
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }


            }
        };
        t.start();
    }

}
public class demo0 {
    public static void main(String[] args) throws InterruptedException {
        MyTMR mytimer = new MyTMR();
        mytimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("这是任务1");
            }
        },3000);//3秒后执行
        mytimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("这是任务2");
            }
        },4000);
        System.out.println("开始计时");
    }
}

3.代码里一些需要注意的地方

为什么扫描线程里判断队首元素时不直接peek?
因为peek不带有阻塞功能,只有put和take带有阻塞功能.
如果直接peek队首元素,如果扫描线程先执行,那么队列为空就会报空指针异常:
在这里插入图片描述

为什么在扫描线程内部调用wait方法?
在这里插入图片描述
代码的逻辑如果是不加wait,如果执行时间还没到,那么就得放回去,然后进入while循环继续取队首元素进行判断,假设我设置的是2个小时之后执行,那么在这两个小时之前就会一直在对队首元素进行判断,也就是在进行忙等,忙等会一直占用CPU,但是这有违背定时器的初衷.
在这里插入图片描述
因此加上带有时间的wait,就可以有效的避免忙等.并且在wait等待期间,如果queue.put又增加任务,queue.put后的notify会将wait唤醒,扫描线程会再一次判断队首元素的执行时间,如果优先级阻塞队列队首的任务的执行时间更早了,那么等待时间就会重新刷新.
在这里插入图片描述
调用wait方法就得上锁,锁范围的问题
漏掉任务的情况:
在这里插入图片描述
出现死锁的情况:
在这里插入图片描述



🌏🌏🌏今天的你看懂这里又学到了很多东西吧🌏🌏🌏

在这里插入图片描述

🌔 🌔 🌔下次见喽🌔 🌔 🌔
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

马上回来了

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值