JavaEE——多线程定时器(Timer)

 定时器是什么?定时器能做什么?本篇文章带你深入了解并实现


目录

一、定时器

 1.1java标准库定时器

1.2定时器代码

二、模拟定时器实现

2.1先创建个任务类,表示执行的任务是啥,任务啥时候执行?

2.2创建定时器类

三、完整代码及注意事项


一、定时器

定时器是什么?

定时器也是软件开发中的一个重要组件. 类似于一个 "闹钟". 达到一个设定的时间之后, 就执行某个指定好的代码
定时器是一种实际开发中非常常用的组件,比如网络通信中, 如果对方 500ms 内没有返回数据, 则断开连接尝试重连,比如一个 Map, 希望里面的某个 key 在 3s 之后过期,类似于这样的场景就需要用到定时器

 1.1java标准库定时器

标准库提供的定时器:Timer在java.util这个集合类中

 

1.2定时器代码

标准库中提供了一个 Timer 类,Timer 类的核心方法为 schedule
schedule 包含两个参数,第一个参数指定即将要执行的任务代码, 第二个参数指定多长时间之后执行 (单位为毫秒)

安排一个工作,这个工作不是立即完成的,而是未来某个时间点~~

public class ThreadDemo6 {
    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        },4000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        },3000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        },2000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        },1000);
    }
}

  

 可见线程还没有结束,那这是因为什么呢?

是因为Timer里面内置了线程,(还是前台线程)会阻止线程结束

二、模拟定时器实现

定时器,内部管理的不仅仅是一个任务,可以管理很多任务的!!

虽然任务有很多,但是他们的触发时间是不同的,每次都找到这些任务中,最先到达的任务执行;一个线程先执行最早的任务,做完了之后再执行第二早的,那么应该用什么去存储这些任务呢?

当然是堆!!!java标准库中提供了带优先级的阻塞队列

  • 队列中的每个元素是一个 Task 对象
  • Task 中带有一个时间属性, 队首元素就是即将要执行的任务
  • 同时有一个 worker 线程一直扫描队首元素, 看队首元素是否需要执行

2.1先创建个任务类,表示执行的任务是啥,任务啥时候执行?

// 表示一个任务.
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;
    }

    @Override
    public int compareTo(MyTask o) {
       return (int) (this.time - o.time);
    }
}

2.2创建定时器类

class MyTimer{
    // 这个结构, 带有优先级的阻塞队列. 核心数据结构
    private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
    // 创建一个锁对象
    private Object locker = new Object();

    // 此处的 delay 是一个形如 3000 这样的数字 (多长时间之后, 执行该任务)
    public void schedule(Runnable runnable,long delay){
        // 根据参数, 构造 MyTask, 插入队列即可.
        MyTask myTask = new MyTask(runnable,delay);
        queue.put(myTask);
        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);
                           locker.wait(myTask.time - curTime);
                       }
                   }
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
        });
        t.start();
    }
}

注意几个点:

1.要让任务类去实现Comparable接口,以至于可以放进PriorityBlockingQueue

2.使用wait去避免忙等,浪费系统资源

3.在放任务的时候,用notify来唤醒线程

三、完整代码及注意事项



/**
 * @author xyk的电脑
 * @version 1.0
 * @description: TODO
 * @date 2023/3/25 16:10
 */
public class ThreadDemo1 {
    public static void main(String[] args) {
        MyTimer myTimer = new MyTimer();
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello4");
            }
        }, 4000);
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello3");
            }
        }, 3000);
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello2");
            }
        }, 2000);
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello1");
            }
        }, 1000);

        System.out.println("hello");
    }

}

// 表示一个任务.
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;
    }

    @Override
    public int compareTo(MyTask o) {
       return (int) (this.time - o.time);
    }
}


class MyTimer{
    // 这个结构, 带有优先级的阻塞队列. 核心数据结构
    private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
    // 创建一个锁对象
    private Object locker = new Object();

    // 此处的 delay 是一个形如 3000 这样的数字 (多长时间之后, 执行该任务)
    public void schedule(Runnable runnable,long delay){
        // 根据参数, 构造 MyTask, 插入队列即可.
        MyTask myTask = new MyTask(runnable,delay);
        queue.put(myTask);
        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);
                           locker.wait(myTask.time - curTime);
                       }
                   }
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
        });
        t.start();
    }
}

注意事项:

1.使用wait来等待,而不是sleep,wait方便随时提前唤醒

2.wait的参数是“超时时间”,时间达到一定程度后,还没有notify就不等,如果时间还没到,就notify立即返回

3.如果将锁加进内部:

会导致新进来的最早的任务“空打一炮”,导致新的任务无法及时执行了;关键要点:多线程的调度是随机的,无序的!!!

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值