JavaEE 第11节 定时器

前言

本篇博客重点介绍定时器的简单实现,帮助理解其底层原理。关于JAVA工具类自带的定时器,只会简单介绍,详细使用参阅官方文档(下文中有官方文档的连接)。

一、什么是定时器

定时器的概念非常简单。

它在软件开发中应用非常广泛。它类似于一个提前设定好的闹钟,到达指定时间后,执行设定好的任务程序。比如在网络通信中,如果在一个时间范围内没有得到响应,那么重新尝试连接网络等。

二、Java标准库中的定时器

标准库中提供了Timer类,核心方法是schedule(用于安排任务给计时器),它有两个参数:

  • TimerTask task:描述要执行的任务,实际是对Runnable的封装
  • long delay:延迟多长时间执行 ms

如图(来自官方文档):

代码演示:

一个Timer实例只会创建一个线程(默认创建的前台线程),因此如果有多个schedule的任务,他们是串行执行的 。关于Timer类的详细信息和使用请参阅oracle官方文档:Timer

如果有在多线程环境下使用计时器的需要,需要用到线程池中的ScheduledExecutorService.具体使用请参阅oracle官方文档:ScheduledExecutorService

三、设计实现一个简单的定时器

这里介绍的计时器,只包含一个线程,并且可能会出现任务阻塞(如果一个任务执行时间过长,后执行的任务可能延迟执行)

java 复制代码
/**
 * 我们会使用优先级队列来判断那个任务最紧急需要先完成,所以需要实现Compareble,
 * 然后重写compareTo方法,用时间戳进行大小比较
 * 优先级队列中,存放的是一个一个要执行的任务
 */
class MyTimerTask implements Comparable<MyTimerTask> {

    //用绝对时间------时间戳来表示时间间隔
    long time;

    //要执行的任务
    Runnable runnable;

    //构造方法
    public MyTimerTask(Runnable runnable, long delay) {

        //执行时间等于当前时间+延迟时间大小
        this.time = System.currentTimeMillis() + delay;

        this.runnable = runnable;
    }

    //用于执行任务
    public void run() {
        runnable.run();
    }

    //获取需要执行的任务的执行时间,等一下有用
    public long getTime() {
        return time;
    }

    @Override
    public int compareTo(MyTimerTask o) {
        //创建一个小根堆,时间到的先执行
        return (int) (this.time - o.time);
    }
}


class MyTimer {

    //把schedule的任务放到这里,然后去除优先级大的任务,判断时间,是否需要执行
    PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
    //创建一个锁对象
    private Object locker = new Object();

    public void schedule(Runnable runnable, long delay) {

        //涉及优先级队列的读写操作,需要加锁保证线程安全
        synchronized (locker) {
            //把runnable和delay封装到myTimerTask中
            MyTimerTask myTimerTask = new MyTimerTask(runnable, delay);

            //放入优先级队列
            queue.offer(myTimerTask);

            //通知唤醒myTimer创建的线程
            locker.notify();
        }
    }

    //构造方法,一旦创建这个实例,开始在优先级队列里面循环获取方法执行(如果到达要执行的时机)
    public MyTimer() {
        Thread t = new Thread(() -> {

            while (true) {
                try {
                    //下面涉及对优先级队列的读写操作,需要保证线程安全
                    synchronized (locker) {

                        //目前没有任务,就一直睡,直到schedule了新的任务
                        while (queue.isEmpty()) {
                            locker.wait();
                        }
                        
                        //有了任务,先看看当前需不需要执行
                        MyTimerTask task=queue.peek();
                        
                        //时间已经到了,或者超时了,就执行
                        if(System.currentTimeMillis()>=task.time){
                            task.run();
                            //一处执行完的任务
                            queue.poll();
                        }else{
                            //没到时间,就睡到任务执行的时候
                            locker.wait(task.time-System.currentTimeMillis());
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        //启动线程
        t.start();
    }
}

public class Test {
    public static void main(String[] args) throws InterruptedException {
        MyTimer myTimer = new MyTimer();
        myTimer.schedule(() -> {
            System.out.println("hello 1000");
        }, 1000);

        myTimer.schedule(() -> {
            System.out.println("hello 2000");
        }, 2000);

        myTimer.schedule(() -> {
            System.out.println("hello 3000");
        }, 3000);

        System.out.println("程序开始运行");
    }
}

执行结果:

此程序需要注意的几个点:

1)不要把下面的while换成if语句:

2)不能使用Thread.slee()替换wait()

原因有2

1、 Thread.sleep在睡眠的时候是不会释放锁的(抱着锁睡) ,也就是睡眠的时候,schedule操作是无法进行的,会造成死锁

2、notify和wait本来就是搭配起来用,实现线程之间通信的。

如果在myTimer线程睡眠的时候,有新的任务进来,notify会重新唤醒线程,而Thread.sleep是没有这个功能的。

相关推荐
七星静香13 分钟前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
Jacob程序员14 分钟前
java导出word文件(手绘)
java·开发语言·word
ZHOUPUYU15 分钟前
IntelliJ IDEA超详细下载安装教程(附安装包)
java·ide·intellij-idea
stewie618 分钟前
在IDEA中使用Git
java·git
Elaine20239133 分钟前
06 网络编程基础
java·网络
G丶AEOM35 分钟前
分布式——BASE理论
java·分布式·八股
落落鱼201336 分钟前
tp接口 入口文件 500 错误原因
java·开发语言
想要打 Acm 的小周同学呀37 分钟前
LRU缓存算法
java·算法·缓存
镰刀出海40 分钟前
Recyclerview缓存原理
java·开发语言·缓存·recyclerview·android面试
阿伟*rui3 小时前
配置管理,雪崩问题分析,sentinel的使用
java·spring boot·sentinel