【多线程基础】 定时器Timer的使用及实现

文章介绍了如何使用Java的Timer进行定时任务管理,并详细讲解了如何自定义一个定时器,包括创建扫描线程、使用优先级队列存储任务以及处理线程同步问题。此外,还提到了仅锁定wait行可能导致的新任务延迟执行的问题,强调了在take和wait之间加锁的重要性。
摘要由CSDN通过智能技术生成

🎉🎉🎉点进来你就是我的人了
博主主页:🙈🙈🙈戳一戳,欢迎大佬指点!

欢迎志同道合的朋友一起加油喔🦾🦾🦾


目录

前言

一.定时器(Timer)的使用

二.定时器的方法

 三.自定义一个定时器

1.首先构建一个MyTask对象,表示一个任务

2.实现一个定时器(内置一个扫描线程)

3.注意,如果我们的锁只锁wait这一行代码会发生一种极端情况

 4.测试代码



前言

     🐳🐳定时器相当于一个任务管理器。有些任务可能现在执行, 有些任务可能过1个小时,甚至很久才会执行。定时器就是对这些任务进行管理监视, 如果一个任务执行时间到了,定时器就会将这个任务执行。 保证所有的任务都会在合适的时间执行。


一.定时器(Timer)的使用

1.自定义一个类继承于TimerTask的类,并重写其run()方法即可。

2.可以采取匿名类的形式,直接重写其run()方法。

二.定时器的方法

TimeTask有一抽象方法run(),其作用就是用来放我们处理的逻辑任务。
Timer有一schedule()方法,重载参数和另外两个方法如下表:

 代码演示:

public class ThreadDemo3 {
    public static void main(String[] args) {
        //定时器
        //Timer内置了线程(前台线程),会阻止进程结束
        Timer timer =new Timer();
        //TimerTask实现了Runnable接口,对Runnable进行了封装,本质上还是Runnable
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("玄武一号任务执行, 执行代号:闪电;  定时时间:3s");
            }
        },3000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("朱雀二号任务执行, 执行代号:暴风;  定时时间:5s");
            }
        },5000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("青龙三号任务执行, 执行代号:狂风;  定时时间:7s");
            }
        },7000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {  //这个方法的执行是靠Timer内部的线程在时间到了之后执行的
                System.out.println("白虎四号任务执行, 执行代号:地震;  定时时间:10s");
            }
        },10000);
        System.out.println("主线程任务执行   未定时"); //主线程main直接执行
    }
}

 三.自定义一个定时器

我们自己实现一个定时器的前提是我们需要弄清楚定时器都有什么:
1.一个扫描线程,负责来判断任务是否到时间需要执行
2.需要有一个数据结构来保存我们定时器中提交的任务

创建一个扫描线程相对比较简单,我们需要确定一个数据结构来保存我们提交的任务,我们提交过来的任务,是由任务和时间组成的,我们需要构建一个MyTask对象,数据结构我们这里使用优先级队列,因为我们的任务是有时间顺序的,具有一个优先级,并且要保证在多线程下是安全的,所以我们这里使用:PriorityBlockingQueue比较合适。

1.首先构建一个MyTask对象,表示一个任务

//表示一个任务
class MyTask implements Comparable<MyTask> {
    //这个是我们要执行任务
    public Runnable runnable;

    //为了方便判定,使用了绝对的时间戳
    public long time;

    public MyTask(Runnable runnable, long delay) {
        this.runnable =runnable;
        //取当前时刻的时间戳+delay,作为该任务实际执行的时间戳
        this.time =System.currentTimeMillis()+delay;    //实例化这个参数,,这个time就是系统时间加上延迟延迟时间
    }

    @Override
    public int compareTo(MyTask o) {
        //这样的写法表示每次取出的是最小元素
        return (int)(this.time - o.time);   //解决优先级阻塞队列的优先级问题
    }
}

这个类实现了Comparable接口并且重写compareTo方法, 指明我们是根据时间来决定这个任务在队列中的优先级.

2.实现一个定时器(内置一个扫描线程)

扫描线程t中包含优先级阻塞队列(小根堆)PriorityBlockingQueue和 循环监管的流程。
MyTimer对象封装了扫描线程线程t 和 任务的添加方法schedule()

关于扫描线程的优化
2.1 循环监控存在一个弊端,那就是一直循环判断, 占用CPU资源。
(假如堆首任务的执行是1小时后, 再次期间监管线程会跑1小时循环判断。)
       
解决方法: 可以通过线程阻塞和唤醒来解决。在下面代码有详细注释和实现。
2.2 如果任务1小时后执行, 我们让扫描线程wait(1小时)并释放锁进行阻塞等待, 但在此期间如果有新的任务添加进来(可能新的任务需要等30分钟就可以执行,堆首元素发生变化) 就会通过notify唤醒wait,这时需要唤醒扫描线程来重新判断新的等待时间。

class MyTimer {
    //创建一个锁对象
    private Object locker =new Object();

    //这个结构,带有优先级的阻塞队列,核心数据结构
    private PriorityBlockingQueue<MyTask> queue =new PriorityBlockingQueue<>();

    //此处的delay形如 3000 这样的数字(多长时间执行该任务)
    public void schedule(Runnable runnable,long delay) {
        //这个方法的作用就是用来实例化MyTask里面的参数的,然后把这个任务插入到优先级阻塞队列中
        MyTask myTask =new MyTask(runnable,delay);
        queue.put(myTask);
        //每次添加任务用notify唤醒wait重新扫描任务
        synchronized (locker) {
            locker.notify();
        }
    }
    
    //在这里构造扫描任务的线程,时间到了负责执行具体任务了
    public MyTimer() {
        Thread t =new Thread(() -> {
            while (true) {
                try {
                    synchronized (locker) {

                        //阻塞队列只有阻塞的入队列和阻塞的出队列,没有阻塞的查看首元素
                        //出队列,也就是取元素,如果当前队列为空就会发生阻塞,那么这个循环就会终止
                        //如果队列不为空,我们就能获取到这个元素
                        MyTask myTask = queue.take();

                        //获取系统时间
                        long curTime = System.currentTimeMillis();

                        //执行任务的时间小于系统时间就开始执行任务
                        if (myTask.time <= curTime) {
                            //时间到了,可以执行任务了
                            myTask.runnable.run();
                        } else {
                            //时间还没到
                            //把刚才取出的任务重新塞回到队列中
                            queue.put(myTask);   //入队列
                            //使用wait等待解决忙等问题,避免cpu浪费资源,同时释放掉锁
                                locker.wait(myTask.time - curTime);
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
    }
}

3.注意,如果我们的锁只锁wait这一行代码会发生一种极端情况

假设我们的扫描线程刚执行完put方法,这个线程就被cpu调度走了,此时我们的另一个线程调用了schedule,添加了新任务,新任务是12:10执行,然后notify,因为我们并没有wait(),所以相当于这里是notify并没有发挥唤醒作用,然后我们的线程调度回来去执行wait()方法,但是我们的时间差仍然是之前算好的时间差,从14:00点到14:30点,这样会导致新加入的任务不能在我预期的时间执行!!!
这里造成这样的问题,是因为我们的take操作和wait操作不是原子的,我们需要在take和wait之间加上锁,保证每次notify的时候,都在wait中。

 4.测试代码

public class ThreadDemo4 {
    public static void main(String[] args) {
        MyTimer myTimer =new MyTimer();
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("玄武一号任务执行, 执行代号:闪电;  定时时间:10s");
            }
        },10000);
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("朱雀二号任务执行, 执行代号:暴风;  定时时间:7s");
            }
        },7000);
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("青龙三号任务执行, 执行代号:狂风;  定时时间:5s");
            }
        },5000);
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("白虎四号任务执行, 执行代号:地震;  定时时间:3s");
            }
        },3000);
        System.out.println("主线程任务执行   未定时"); //主线程main直接执行
    }

 需要注意理解的是:这段代码一共有两个线程在同时执行,main线程就是那个添加任务的线程,t线程一直就做一件事,就是在循环里一直扫描判断是否需要执行任务

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

书生-w

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

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

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

打赏作者

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

抵扣说明:

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

余额充值