Java多线程案例之定时器详解

 更新时间:2022年10月19日 08:34:30   作者:Moon Bay  
定时器也是软件开发中的一个重要组件. 类似于一个 “闹钟”. 达到一个设定的时间之后, 就执行某个指定好的代码。本文就来和大家详细聊聊定时器的原理与使用,感兴趣的可以了解一下

一.什么是定时器

定时器也是软件开发中的一个重要组件. 类似于一个 “闹钟”. 达到一个设定的时间之后, 就执行某个指定好的代码

定时器是一种实际开发中非常常用的组件,我们举几个例子:

1.比如网络通信中, 如果对方 500ms 内没有返回数据, 则断开连接尝试重连

2.比如一个 Map, 希望里面的某个 key 在 3s 之后过期(自动删除)

以上类似于这样的场景就需要用到定时器

二.标准库中的定时器(timer)

2.1什么是定时器

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

Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello");
}
}, 3000);

2.2定时器的使用

Timer的构造方法

构造方法说明
public Timer()无参数构造方法,默认定时器关联的线程不是守护线程,线程名字也是默认值
public Timer(boolean isDaemon)指定定时器中关联的线程是否为守护线程,如果是,参数为true
public Timer(String name)指定定时器关联线程名称,线程类型默认为非守护线程
public Timer(String name, boolean isDaemon)指定定时器关联线程名和线程类型

Timer方法

方法说明
public void schedule (TimerTask task, long delay)指定任务,延迟多久执行该任务
public void schedule(TimerTask task, Date time)指定任务,指定任务的执行时间
public void schedule(TimerTask task, long delay, long period)连续执行指定任务,延迟时间,连续执行任务的时间间隔,毫秒为单位
public void schedule(TimerTask task, Date firstTime, long period)连续执行指定任务,第一次任务的执行时间,连续执行任务的时间间隔
public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period)连续执行指定任务,第一次任务的执行时间,连续执行任务的时间间隔
public void scheduleAtFixedRate(TimerTask task, long delay, long period)连续执行指定任务,延迟时间,连续执行任务的时间间隔,毫秒为单位
public void cancel()终止定时器所有任务,终止执行的任务不受影响

TimerTask是专门来实现Runnable接口的

下面我们会实现一下定时器,我们就不用TimerTask了,我们直接使用Runnable,因为TimerTask实现了Runnable接口,所以后面测试我们自己所写的schedule方法时,也可以传入TimerTask类型的引用,既然是简单地实现,那就不实现连续执行的功能了。.

public class Test {
    public static void main(String[] args){
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("执行线程在5s后执行");
            }
        },5000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("执行线程在2s后执行");
            }
        },2000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("执行线程在3s后执行");
            }
        },3000);
    }
}

三.实现定时器

3.1什么是定时器

定时器的构成:一个带优先级的阻塞队列

为啥要带优先级呢?

因为阻塞队列中的任务都有各自的执行时刻 (delay). 最先执行的任务一定是 delay 最小的. 使用带优先级的队列就可以高效的把这个 delay 最小的任务找出来.

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

class MyTask implements Comparable<MyTask>{
    //执行的时间戳
    private long time;
    //接受具体任务
    private Runnable runnable;
    //创建MyTask构造方法
    public MyTask(Runnable runnable,long time) {
        //通过currentTimeMillis来获取time 中存的是绝对时间, 超过这个时间的任务就应该被执行
        this.time = System.currentTimeMillis()+time;
        this.runnable = runnable;
    }
    //执行任务
    public void run(){
        this.runnable.run();
    }
    //提供对外time
    public long getTime() {
        return time;
    }
    //执行comparable接口来进行时间的比较,并将time的long类型转换为int类型
    @Override
    public int compareTo(MyTask o) {
        return (int)(this.time-o.time);
    }
}

Timer 实例中, 通过 PriorityBlockingQueue 来组织若干个 Task 对象.通过 schedule 来往队列中插入一个个 Task 对象.

class MyTimer{
    // 定时器内部要能够存放多个任务
    private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
    //为锁创建一个对象
    Object locker = new Object();
    public void schedule(Runnable runnable, long delay) {
        MyTask task = new MyTask(runnable, delay);
        queue.put(task);
        // 每次任务插入成功之后, 都唤醒一下扫描线程, 让线程重新检查一下队首的任务看是否时间到要执行~~
        synchronized (locker) {
            locker.notify();
        }
    }

Timer 类中存在一个 worker 线程, 一直不停的扫描队首元素, 看看是否能执行这个任务.所谓 “能执行” 指的是该任务设定的时间已经到达了

class MyTimer{
    // 定时器内部要能够存放多个任务
    private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
    //为锁创建一个对象
    Object locker = new Object();
    public void schedule(Runnable runnable, long delay) {
        MyTask task = new MyTask(runnable, delay);
        queue.put(task);
        // 每次任务插入成功之后, 都唤醒一下扫描线程, 让线程重新检查一下队首的任务看是否时间到要执行~~
        synchronized (locker) {
            locker.notify();
        }
    }
    public MyTimer() {
        Thread t = new Thread(() -> {
            while (true) {
                try {
                    // 先取出队首元素
                    MyTask task = queue.take();
                    // 再比较一下看看当前这个任务时间到了没?
                    long curTime = System.currentTimeMillis();
                    if (curTime < task.getTime()) {
                        // 时间没到, 把任务再塞回到队列中.
                        queue.put(task);
                        // 指定一个等待时间,防止有的线程需要等待时间很长,但是线程一直运行等待时间到来执行,这样会占有CPU占有资源
                        synchronized (locker) {
                            locker.wait(task.getTime() - curTime);
                        }
                    } else {
                        // 时间到了, 执行这个任务
                        task.run();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
    }
}

3.2最终实现代码

package thread;

import java.util.PriorityQueue;
import java.util.concurrent.PriorityBlockingQueue;

// 创建一个类, 表示一个任务.
class MyTask implements Comparable<MyTask> {
    // 任务具体要干啥
    private Runnable runnable;
    // 任务具体啥时候干. 保存任务要执行的毫秒级时间戳
    private long time;

    // after 是一个时间间隔. 不是绝对的时间戳的值
    public MyTask(Runnable runnable, long delay) {
        this.runnable = runnable;
        this.time = System.currentTimeMillis() + delay;
    }

    public void run() {
        runnable.run();
    }

    public long getTime() {
        return time;
    }

    @Override
    public int compareTo(MyTask o) {
        // 到底是谁见谁, 才是一个时间小的在前? 需要咱们背下来.
        return (int) (this.time - o.time);
    }
}

class MyTimer {
    // 定时器内部要能够存放多个任务
    private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();

    public void schedule(Runnable runnable, long delay) {
        MyTask task = new MyTask(runnable, delay);
        queue.put(task);
        // 每次任务插入成功之后, 都唤醒一下扫描线程, 让线程重新检查一下队首的任务看是否时间到要执行~~
        synchronized (locker) {
            locker.notify();
        }
    }

    private Object locker = new Object();

    public MyTimer() {
        Thread t = new Thread(() -> {
            while (true) {
                try {
                    // 先取出队首元素
                    MyTask task = queue.take();
                    // 再比较一下看看当前这个任务时间到了没?
                    long curTime = System.currentTimeMillis();
                    if (curTime < task.getTime()) {
                        // 时间没到, 把任务再塞回到队列中.
                        queue.put(task);
                        // 指定一个等待时间
                        synchronized (locker) {
                            locker.wait(task.getTime() - curTime);
                        }
                    } else {
                        // 时间到了, 执行这个任务
                        task.run();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
    }
}

public class Test {
    public static void main(String[] args) {
        MyTimer myTimer = new MyTimer();
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello timer!");
            }
        }, 3000);
        System.out.println("main");
    }
}

以上就是Java多线程案例之定时器详解的详细内容,更多关于Java多线程 定时器的资料请关注脚本之家其它相关文章!

相关文章

  • 关于BigDecimal类型数据的绝对值和相除求百分比

    关于BigDecimal类型数据的绝对值和相除求百分比

    这篇文章主要介绍了关于BigDecimal类型数据的绝对值和相除求百分比,Java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算,需要的朋友可以参考下
    2023-07-07
  • Springboot 对接支付宝实现扫码支付功能

    Springboot 对接支付宝实现扫码支付功能

    本文介绍了如何在Spring Boot项目中实现支付宝支付功能,包括沙箱环境配置、依赖引入、配置参数、订单类定义、测试接口编写等步骤,通过不同场景下的请求方式(PC端、二维码、回调处理、定时查询支付结果),展示了如何与支付宝API进行交互,感兴趣的朋友一起看看吧
    2025-03-03
  • java查找文件夹下最新生成的文件的方法

    java查找文件夹下最新生成的文件的方法

    在本篇文章中我们给大家分享了关于java怎么查找文件夹下最新生成的文件的相关方法和知识点,有需要的朋友们参考下。
    2019-07-07
  • Java StackTraceElement实例代码

    Java StackTraceElement实例代码

    这篇文章主要介绍了Java StackTraceElement实例代码,分享了相关代码示例,小编觉得还是挺不错的,具有一定借鉴价值,需要的朋友可以参考下
    2018-02-02
  • maven-surefire-plugin总结示例详解

    maven-surefire-plugin总结示例详解

    这篇文章主要介绍了maven-surefire-plugin总结,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-07-07
  • java邮件发送简单实现代码

    java邮件发送简单实现代码

    这篇文章主要为大家详细介绍了java邮件发送简单实现代码,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-03-03
  • Java实战练习之扑克牌魔术

    Java实战练习之扑克牌魔术

    这篇文章主要介绍了Java实战练习之扑克牌魔术,文中有非常详细的代码示例,对正在学习java的小伙伴们有很好地帮助,需要的朋友可以参考下
    2021-04-04
  • java实现清理DNS Cache的方法

    java实现清理DNS Cache的方法

    这篇文章主要介绍了java实现清理DNS Cache的方法,分析了几种常用的清理方法,并给出了反射清理的完整实例,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-01-01
  • SpringBoot集成分页插件PageHelper的配置和使用过程

    SpringBoot集成分页插件PageHelper的配置和使用过程

    这篇文章主要介绍了SpringBoot集成分页插件PageHelper的配置和使用过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-04-04
  • 搭建Spring Boot聚合项目的实现示例

    搭建Spring Boot聚合项目的实现示例

    本文主要介绍了搭建Spring Boot聚合项目的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-04-04

最新评论