当前位置: 首页 > article >正文

JavaEE之定时器及自我实现

在生活当中,有很多事情,我们不是立马就去做,而是在规定了时间之后,在到该时间时,再去执行,比如:闹钟、定时关机等等,在程序的世界中,有些代码也不是立刻执行,那么我们该如何实现呢?一探究竟——>《定时器》

1. 定时器

定时器是什么

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

在Java当中也给我们提供了定时器(Timer)的类,请见下文。

标准库中的定时器

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

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2. 自我实现一个定时器

1.首先定时器是用于处理任务的,我们该如何在定时器当中管理任务呢??

我们通过一个类,描述任务和任务执行的时间
具体任务的逻辑用Runnble表示,执行时间的可以用一个long型delay去表示

/**
 * 任务类
 */
 //由于需要比较时间大小,所以使用接口
class MyTask implements Comparable<MyTask>{

    //任务
    private Runnable runnable = null;
    //延迟时间
    private long time = 0;
    
    public MyTask(Runnable runnable, long delay) {
        //任务不能为空
        if(runnable==null){
            throw new RuntimeException("任务不能为空...");
        }
        //时间不能为负数
        if(delay<0){
            throw new RuntimeException("时间不能为负数...");
        }
        this.runnable = runnable;
        // 计算出任务执行的具体时间
        this.time = delay+System.currentTimeMillis();
    }

    public Runnable getRunnable() {
        return runnable;
    }

    public long getTime() {
        return time;
    }

    //比较当前任务和其他任务的时间
    @Override
    public int compareTo(MyTask o) {
        return (int) (o.getTime()-this.getTime());
    }
}

2.通过MyTask描述了任务之后,由于任务的执行顺序不一样,我们该如何去管理任务呢?

我们通过一个优先级队列把任务的对象组织起来

//用阻塞队列来管理任务
private BlockingQueue<MyTask> queue=new PriorityBlockingQueue<>();

3.我们描述完了任务也通过优先级队列管理了任务对象,我们如何让任务对象和定时器关联起来呢?

    /**
     * 添加任务的方法
     * @param runnable 任务
     * @param delay 延时
     * @throws InterruptedException
     */
    public void schedule(Runnable runnable, long delay) throws InterruptedException {
        // 根据传处的参数,构造一个MyTask
        MyTask task=new MyTask(runnable,delay);
        // 把任务放入阻塞队列
        queue.put(task);
    }
}

4.我们通过schedule方法把任务对象添加到了阻塞队列当中,我们只需要创建一个线程来执行任务即可

此时我们的思路是:创建一个线程不停的扫描任务,取出队列的首元素若时间到就取出执行,时间没到就放回队列不执行,就能写出以下代码:

    // 创建扫描线程
    Thread thread=new Thread(()->{
        //不断的扫描队列中的任务
        while (true){
            try {
                //1.从阻塞队列中获取任务
                MyTask task = queue.take();
                //2.判断到没到执行时间
                long currentTime=System.currentTimeMillis();
                if(currentTime>=task.getTime()){
                    //时间到了就执行任务
                    task.getRunnable().run();
                    }else {
                    // 没有到时间,重新放回队列
                    queue.put(task);
                }
            }catch (InterruptedException e) {
                e.printStackTrace();
            }
        }},"scanThread");
        //启动线程
        thread.start();      

但是上面的代码有一个很明显的问题,就是 “忙等” ,为什么呢?
在这里插入图片描述
那么我们怎么解决这个忙等这个问题呢?

在放回队列时让程序等待一段时间等待一段时间
时间为:下一个任务的执行时间和当前时间的差
那么既然要等待了我们必须要通过持有同一个锁,来完成等待操作,所以我们创建一把锁

修改代码如下:

// 创建扫描线程
Thread thread=new Thread(()->{
    //不断的扫描队列中的任务
    while (true){
        try {
            //1.从阻塞队列中获取任务
            MyTask task = queue.take();
            //2.判断到没到执行任务的时间
            long currentTime=System.currentTimeMillis();
            if(currentTime>=task.getTime()){
                //时间到了就执行任务
                task.getRunnable().run();
            }else {
                // 当前时间与任务执行时间的差
                long waitTime = task.getTime() - currentTime;
                // 没有到时间,重新放回队列
                queue.put(task);
                synchronized (locker){
                    //等时间
                    locker.wait(waitTime);
                }
            }
        }catch (InterruptedException e) {
            e.printStackTrace();
        }
    }},"scanThread");
     //启动线程,真正去系统中申请资源
    thread.start();

通过锁,解决了忙等问题,

5.此时还有一个新的问题,在该队列中若产生了新的任务执行时间在等待任务之前该怎么办呢?

我们在每一次向阻塞队列当中添加新任务时,我们就唤醒一次扫描线程即可

/**
     * 添加任务的方法
     * @param runnable 任务
     * @param delay 延时
     * @throws InterruptedException
     */
    public void schedule(Runnable runnable, long delay) throws InterruptedException {
        // 根据传处的参数,构造一个MyTask
        MyTask task=new MyTask(runnable,delay);
        // 把任务放入阻塞队列
        queue.put(task);
        //在每次添加新任务时,唤醒一次扫描线程,以访扫描线程还在等待,新任务时间过了的问题
        synchronized (locker){
            locker.notifyAll();
        }
    }
}

6.CPU调度的过程中可能会产生执行顺序的问题,或当一个线程执行到一半的时间被调度走的现象,会引发什么问题呢?

在这里插入图片描述

造成该现象的原因是没有保证原子性,我们扩大锁范围即可解决该问题,修改后的代码如下:

//不断的扫描队列中的任务
            while (true){
                synchronized (locker){
                    try {
                        //1.从阻塞队列中获取任务
                        MyTask task = queue.take();
                        //2.判断到没到执行任务的时间
                        long currentTime=System.currentTimeMillis();
                        if(currentTime>=task.getTime()){
                            //时间到了就执行任务
                            task.getRunnable().run();
                        }else {
                            // 当前时间与任务执行时间的差
                            long waitTime = task.getTime() - currentTime;
                            // 没有到时间,重新放回队列
                            queue.put(task);
                            locker.wait(waitTime);
                        }
                    }catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
              
            }

7.由于进入锁之后,MyTask task = queue.take();操作,当阻塞队列中没有元素时,就会阻塞等待,直到队列中有可用元素才继续执行,但是由于MyTask task = queue.take();操作持有了锁,导致无法释放锁,添加任务的方法又迟迟取不到锁,导致一个在等着任务执行,一个在等着获取锁添加任务,造成了“死锁”现象,我们该如何解决呢?

我们发现在为了解决原子性问题时,我们扩大加锁的范围,却又引入了更大的问题
一般我们两害相全取其轻

为了解决无法及时执行任务的问题,我们创建了一个后台的扫描线程,只做定时唤醒操作,定时1s或者任意时间唤醒执行一次


完整的定时器实现代码如下:
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * 自我实现定时器
 */
public class MyTimer {
    //用阻塞队列来管理任务
    private BlockingQueue<MyTask> queue=new PriorityBlockingQueue<>();
    //创建⼀个锁对象
    private Object locker = new Object();
    public MyTimer() throws InterruptedException {
        // 创建扫描线程
        Thread thread=new Thread(()->{
            //不断的扫描队列中的任务
            while (true){
                synchronized (locker){
                    try {
                        //1.从阻塞队列中获取任务
                        MyTask task = queue.take();
                        //2.判断到没到执行任务的时间
                        long currentTime=System.currentTimeMillis();
                        if(currentTime>=task.getTime()){
                            //时间到了就执行任务
                            task.getRunnable().run();
                        }else {
                            // 当前时间与任务执行时间的差
                            long waitTime = task.getTime() - currentTime;
                            // 没有到时间,重新放回队列
                            queue.put(task);
                            locker.wait(waitTime);
                        }
                    }catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            }
        },"scanThread");
        //启动线程,真正去系统中申请资源
        thread.start();

        //创建一个后台线程
        Thread daemonThread= new Thread(()->{
            while (true){
                //定时唤醒
                synchronized (locker){
                    locker.notifyAll();
                }
                //休眠一会
                try {
                    TimeUnit.MICROSECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        //设置成后台线程
        daemonThread.setDaemon(true);
        //启动线程
        daemonThread.start();
    }


    /**
     * 添加任务的方法
     * @param runnable 任务
     * @param delay 延时
     * @throws InterruptedException
     */
    public void schedule(Runnable runnable, long delay) throws InterruptedException {
        // 根据传处的参数,构造一个MyTask
        MyTask task=new MyTask(runnable,delay);
        // 把任务放入阻塞队列
        queue.put(task);
        synchronized (locker){
            locker.notifyAll();
        }
    }
}

/**
 * 任务类
 */
class MyTask implements Comparable<MyTask>{

    //任务
    private Runnable runnable = null;
    //延迟时间
    private long time = 0;
    public MyTask(Runnable runnable, long delay) {
        //任务不能为空
        if(runnable==null){
            throw new RuntimeException("任务不能为空...");
        }
        //时间不能为负数
        if(delay<0){
            throw new RuntimeException("时间不能为负数...");
        }
        this.runnable = runnable;
        // 计算出任务执行的具体时间
        this.time = delay+System.currentTimeMillis();
    }


    public Runnable getRunnable() {
        return runnable;
    }


    public long getTime() {
        return time;
    }


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

http://www.kler.cn/a/473078.html

相关文章:

  • JS进阶--JS听到了不灭的回响
  • 数据结构(1~10)
  • Visio 画阀门 符号 : 电动阀的画法
  • 这是什么操作?强制迁移?GitLab 停止中国区用户访问
  • MakeFile使用指南
  • flink cdc oceanbase(binlog模式)
  • 闲谭SpringBoot--ShardingSphere分库分表探究
  • spring mvc源码学习笔记之一
  • Java高频面试之SE-10
  • 单片机-外部中断
  • springboot 集成 etcd
  • YCM上告警项怎么修改
  • 【转载记录】Scikit-learn 报错:’super’ object has no attribute ‘__sklearn_tags__’.
  • Perl语言的语法糖
  • 【HTML+CSS+JS+VUE】web前端教程-2-HTML5介绍和基础骨架
  • 【2024华为OD-E卷-100分-boss的收入】(题目+思路+JavaC++Python解析)
  • 【并发篇】CompletableFuture学习
  • JavaScript 正则表达式
  • 代码随想录算法训练营第1天(数组1)| 704. 二分查找、27. 移除元素、977.有序数组的平方
  • 【数据库】SQL相关知识点总结1(数据库约束、三大范式、关系模型、聚合函数)
  • 为什么页面无法正确显示?都有哪些HTML和CSS相关问题?
  • PHP语言的函数实现
  • svelte5中使用react组件
  • 跨界融合:人工智能与区块链如何重新定义数据安全?
  • MATLAB语言的软件工程
  • c#13新特性