文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Java多线程案例之定时器

2023-09-10 20:18

关注

文章目录

一. 定时器概述

1. 什么是定时器

定时器是一种实际开发中非常常用的组件, 类似于一个 “闹钟”, 达到一个设定的时间之后, 就执行某个指定好的代码.

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

2. 标准库中的定时器

标准库中提供了一个 Timer 类, Timer 类的核心方法为schedule.

Timer类构造时内部会创建线程, 有下面的四个构造方法, 可以指定线程名和是否将定时器内部的线程指定为后台线程(即守护线程), 如果不指定, 定时器对象内部的线程默认为前台线程.

序号构造方法解释
1public Timer()无参, 定时器关联的线程为前台线程, 线程名为默认值.
2public Timer(boolean isDaemon)指定定时器中关联的线程类型, true(后台线程), false(前台线程).
3public Timer(String name)指定定时器关联的线程名, 线程类型为前台线程
4public Timer(String name, boolean isDaemon)指定定时器关联的线程名和线程类型

schedule 方法是给Timer注册一个任务, 这个任务在指定时间后进行执行, TimerTask类就是专门描述定时器任务的一个抽象类, 它实现了Runnable接口.

public abstract class TimerTask implements Runnable // jdk源码
序号方法解释
1public void schedule(TimerTask task, long delay)指定任务, 延迟多久执行该任务
2public void schedule(TimerTask task, Date time)指定任务, 指定任务的执行时间
3public void schedule(TimerTask task, long delay, long period)连续执行指定任务, 延迟时间, 连续执行任务的时间间隔, 毫秒为单位
4public void schedule(TimerTask task, Date firstTime, long period)连续执行指定任务, 第一次任务的执行时间, 连续执行任务的时间间隔
5public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period)与方法4作用相同
6public void scheduleAtFixedRate(TimerTask task, long delay, long period)与方法3作用相同
7public void cancel()清空任务队列中的全部任务, 正在执行的任务不受影响.

代码示例:

import java.util.Timer;import java.util.TimerTask;public class TestProgram {    public static void main(String[] args) {        Timer timer = new Timer();        timer.schedule(new TimerTask() {            @Override            public void run() {                System.out.println("执行延后3s的任务!");            }        }, 3000);        timer.schedule(new TimerTask() {            @Override            public void run() {                System.out.println("执行延后2s后的任务!");            }        }, 2000);                timer.schedule(new TimerTask() {            @Override            public void run() {                System.out.println("执行延后1s的任务!");            }        }, 1000);    }}

执行结果:

img

观察执行结果, 任务执行结束后程序并没有结束, 即进程并没有结束, 这是因为上面的代码定时器内部是开启了一个线程去执行任务的, 虽然任务执行完成了, 但是该线程并没有销毁; 这和自己定义一个线程执行完成 run 方法后就自动销毁是不一样的, Timer 本质上是相当于线程池, 它缓存了一个工作线程, 一旦任务执行完成, 该工作线程就处于空闲状态, 等待下一轮任务.

二. 定时器的简单实现

首先, 我们需要定义一个类, 用来描述一个定时器当中的任务, 类要成员要有一个Runnable, 再加上一个任务执行的时间戳, 具体还包含如下内容:

class MyTask implements Comparable<MyTask>{    //要执行的任务    private Runnable runnable;    //任务的执行时间    private long time;    public MyTask(Runnable runnable, long time) {        this.runnable = runnable;        this.time = time;    }    //获取当前任务的执行时间    public long getTime() {        return this.time;    }    //执行任务    public void run() {        runnable.run();    }    @Override    public int compareTo(MyTask o) {        return (int) (this.time - o.time);    }}

然后就需要实现定时器类了, 我们需要使用一个数据结构来组织定时器中的任务, 需要每次都能将时间最早的任务找到并执行, 这个情况我们可以考虑用优先级队列(即小根堆)来实现, 当然我们还需要考虑线程安全的问题, 所以我们选用优先级阻塞队列 PriorityBlockingQueue 是最合适的, 特别要注意在自定义的任务类当中要实现比较方式, 或者实现一下比较器也行.

private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();

我们自己实现的定时器类中要有一个注册任务的方法, 用来将任务插入到优先级阻塞队列中;

还需要有一个线程用来执行任务, 这个线程是从优先级阻塞队列中取出队首任务去执行, 如果这个任务还没有到执行时间, 那么线程就需要把这个任务再放会队列当中, 然后线程就进入等待状态, 线程等待可以使用sleepwait, 但这里有一个情况需要考虑, 当有新任务插入到队列中时, 我们需要唤醒线程重新去优先级阻塞队列拿队首任务, 毕竟新注册的任务的执行时间可能是要比前一阵拿到的队首任务时间是要早的, 所以这里使用wait进行进行阻塞更合适, 那么唤醒操作就需要使用notify来实现了.

实现代码如下:

//自己实现的定时器类class MyTimer {    //扫描线程    private Thread t = null;    //阻塞队列,存放任务    private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();    public MyTimer() {        //构造扫描线程        t = new Thread(() -> {           while (true) {               //取出队首元素,检查队首元素执行任务的时间               //时间没到,再把任务放回去               //时间到了,就执行任务               try {                   synchronized (this) {                       MyTask task = queue.take();                       long curTime = System.currentTimeMillis();                       if (curTime < task.getTime()) {                           //时间没到,放回去                           queue.put(task);                           //放回任务后,不应该立即就再次取出该任务                           //所以wait设置一个阻塞等待,以便新任务到时间或者新任务来时后再取出来                           this.wait(task.getTime() - curTime);                       } else {                           //时间到了,执行任务                           task.run();                       }                   }               } catch (InterruptedException e) {                   throw new RuntimeException(e);               }           }        });        t.start();    }        public void schedule (Runnable runnable, long after) {        //获取当前时间的时间戳再加上任务时间        MyTask task = new MyTask(runnable, System.currentTimeMillis() + after);        queue.put(task);        //每次当新任务加载到阻塞队列时,需要中途唤醒线程,因为新进来的任务可能是最早需要执行的        synchronized (this) {            this.notify();        }    }}

要注意上面扫描线程中的synchronized并不能只要针对wait方法加锁, 如果只针对wait加锁的话, 考虑一个极端的情况, 假设的扫描线程刚执行完put方法, 这个线程就被cpu调度走了, 此时另有一个线程在队列中插入了新任务, 然后notify唤醒了线程, 而刚刚并没有执行wait阻塞, notify就没有起到什么作用, 当cpu再调度到这个线程, 这样的话如果新插入的任务要比原来队首的任务时间更早, 那么这个新任务就被错过了执行时间, 这些线程安全问题真是防不胜防啊, 所以我们需要保证这些操作的原子性, 也就是上面的代码, 扩大锁的范围, 保证每次notify都是有效的.

那么最后基于上面的代码, 我们来测试一下这个定时器:

public class TestDemo23 {    public static void main(String[] args) {        MyTimer timer = new MyTimer();        timer.schedule(new Runnable() {            @Override            public void run() {                System.out.println("2s后执行的任务1");            }        }, 2000);        timer.schedule(new Runnable() {            @Override            public void run() {                System.out.println("2s后执行的任务1");            }        }, 1000);    }}

执行结果:

img

来源地址:https://blog.csdn.net/Trong_/article/details/128757224

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯