Java Web 实战 09 - 多线程基础之定时器
创始人
2024-06-02 17:04:36
0

多线程当中的定时器

  • 定时器
  • 1. 官方的定时器
  • 2. 自己实现的计时器
    • 2.1 使用优先级阻塞队列
    • 2.2 定义比较规则
    • 2.3 解决 CPU 空转 : wait
    • 2.4 防止其他线程插入到入队列和线程等待之间
    • 2.5 队列初识为空的情况下 , 需要阻塞等待

本篇文章 , 给大家带来的是多线程当中的定时器 , 定时器是一个线程工具,可以用于调度多个定时任务以后台线程的方式运行。
比如说,在开发中,我们需要做一些周期性的操作,比如每隔三分钟就清空一次文件夹,用定闹钟的方式人为的清空肯定是不合适的。这时就可以通过 Timer 和 TimerTask 类来实现,定时完成具体任务的功能。
大家可以跳转到此页面观看效果更加
上一篇文章的链接也给大家贴在这里了
在这里插入图片描述

定时器

1. 官方的定时器

import java.util.Timer;
import java.util.TimerTask;public class Demo22 {public static void main(String[] args) {// java.util 里面的一个组件Timer timer = new Timer();// schedule 这个方法的效果是:"安排一个任务"// 第一个参数:new TimerTask() + 重写的 run , 表示任务// 第二个参数:多少毫秒之后开始执行任务timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("这是一个要执行的任务");}},3000);}
}

image.png
那这是怎么回事呢 ?
实现定时器 , 背后涉及到多线程 !
Timer 里面有线程 , 这个线程的运行阻止了进程的退出

那为什么不使用 sleep 呢 ?
使用 sleep 是把当前线程给阻塞了 .
sleep 的时间里 , 你啥都干不了 , 就只能干等
但是使用定时器 , 之前的线程该干啥干啥

import java.util.Timer;import java.util.TimerTask;public class Demo22 {public static void main(String[] args) throws InterruptedException {// java.util 里面的一个组件Timer timer = new Timer();// schedule 这个方法的效果是:"安排一个任务"// 第一个参数:new TimerTask() + 重写的 run , 表示任务// 第二个参数:多少毫秒之后开始运行timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("这是一个要执行的任务");}},3000);while(true) {System.out.println("其他线程该干啥干啥");Thread.sleep(1000);}}
}

image.png

2. 自己实现的计时器

Timer 其实可以往里面加入很多任务的
我们要实现的计时器 , 就需要考虑下面几个因素

  • Timer内部要组织很多的任务
  • Timer里的每个任务都要通过一定的方式来描述出来 (自己定义一个 TimerTask 类)
  • 还需要有一个线程 , 通过这个线程来扫描定时器内部的任务 , 执行其中时间到了的任务

虽然这里的任务有很多 , 但是他们的执行顺序是一定的 , 按照时间顺序先后来执行 , 所以我们可以使用优先级队列来组织这些任务 , 时间小的 (time 属性小的) 先执行
但是使用优先级队列还有可能出现问题 :
这个队列会被多个线程同时访问

  1. schedule 可能是在多线程环境中被调用 , 每次调用都要往队列中添加元素
  2. 内部还需要有专门的线程来执行队列里的任务

读和写同步进行 , 就可能会造成线程不安全问题
所以我们选择使用 PriorityBlockingQueue
他既满足了优先级队列的特点 : 先进先出 , 又满足了阻塞队列的特点 : 线程安全

2.1 使用优先级阻塞队列

import java.util.concurrent.PriorityBlockingQueue;// 描述任务的类
class MyTask {// 任务要干啥private Runnable command;// 任务什么时候执行private long time;public MyTask(Runnable command,long after) {this.command = command;// 任务中需要记录一个完整的时间// 记录到底什么时候(几分几秒)执行// 比如 2023-01-30 09:24this.time = System.currentTimeMillis() + after;}// 执行任务的方法,直接在内部调用 command 的 run 方法public void run() {command.run();}
}// 自己创建的定时器类
class MyTimer {// 使用优先级队列来保存若干个任务private PriorityBlockingQueue queue = new PriorityBlockingQueue<>();// command 代表要执行的任务// after 代表多长时间之后来执行这个任务public void schedule(Runnable command,long after) {// 创建任务MyTask myTask = new MyTask(command, after);// 把任务添加到优先级阻塞队列中queue.put(myTask);}
}public class Demo23 {
}

接下来 , 我们就需要安排线程来执行队列中的任务 . 尤其是每次出队列 , 就要执行时间最短的任务

import java.util.concurrent.PriorityBlockingQueue;// 描述任务的类
class MyTask {// 任务要干啥private Runnable command;// 任务什么时候执行// 设置成 public 方便下面访问// 提供他的 public 的 get 方法也可以public long time;public MyTask(Runnable command,long after) {this.command = command;// 任务中需要记录一个完整的时间// 记录到底什么时候(几分几秒)执行// 比如 2023-01-30 09:24this.time = System.currentTimeMillis() + after;}// 执行任务的方法,直接在内部调用 command 的 run 方法public void run() {command.run();}
}// 自己创建的定时器类
class MyTimer {// 使用优先级队列来保存若干个任务private PriorityBlockingQueue queue = new PriorityBlockingQueue<>();// command 代表要执行的任务// after 代表多长时间之后来执行这个任务public void schedule(Runnable command,long after) {// 创建任务MyTask myTask = new MyTask(command, after);// 把任务添加到优先级阻塞队列中queue.put(myTask);}public MyTimer() {// 在这里启动一个线程Thread t = new Thread(() -> {while(true) {// 循环过程中,不停地从队列中获取到队首元素// 我们首先需要判定队首元素当前时间是否就绪;就绪->执行,不就绪->不执行// 比如这个任务是 12:30 的 , 现在是 12:15 , 就先不执行try {// 把任务取出来MyTask myTask = queue.take();long curTime = System.currentTimeMillis();if(curTime < myTask.time) {// 时间还未到// 再把它放回去queue.put(myTask);} else {// 时间到了// 执行任务myTask.run();}} catch (InterruptedException e) {e.printStackTrace();}}});}
}public class Demo23 {public static void main(String[] args) {MyTimer myTimer = new MyTimer();myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("任务1");}},2000);myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("任务2");}},4000);myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("任务3");}},6000);}
}

我们运行一下
image.png
这是因为我们没定义优先级的比较规则

2.2 定义比较规则

import java.util.concurrent.PriorityBlockingQueue;// 描述任务的类
class MyTask implements Comparable {// 任务要干啥private Runnable command;// 任务什么时候执行// 设置成 public 方便下面访问// 提供他的 public 的 get 方法也可以private long time;public MyTask(Runnable command,long after) {this.command = command;// 任务中需要记录一个完整的时间// 记录到底什么时候(几分几秒)执行// 比如 2023-01-30 09:24this.time = System.currentTimeMillis() + after;}// 执行任务的方法,直接在内部调用 command 的 run 方法public void run() {command.run();}public long getTime() {return time;}@Overridepublic int compareTo(MyTask o) {// 希望时间小的在前面// 试一下就知道了// this.time - o.time 得到的是 long,返回值是int// 所以需要强转return (int) (this.time - o.time);}
}// 自己创建的定时器类
class MyTimer {// 使用优先级队列来保存若干个任务private PriorityBlockingQueue queue = new PriorityBlockingQueue<>();// command 代表要执行的任务// after 代表多长时间之后来执行这个任务public void schedule(Runnable command,long after) {// 创建任务MyTask myTask = new MyTask(command, after);// 把任务添加到优先级阻塞队列中queue.put(myTask);}public MyTimer() {// 在这里启动一个线程Thread t = new Thread(() -> {while(true) {// 循环过程中,不停地从队列中获取到队首元素// 我们首先需要判定队首元素当前时间是否就绪;就绪->执行,不就绪->不执行// 比如这个任务是 12:30 的 , 现在是 12:15 , 就先不执行try {// 把任务取出来MyTask myTask = queue.take();long curTime = System.currentTimeMillis();if(curTime < myTask.getTime()) {// 时间还未到// 再把它放回去queue.put(myTask);} else {// 时间到了// 执行任务myTask.run();}} catch (InterruptedException e) {e.printStackTrace();}}});t.start();}
}public class Demo23 {public static void main(String[] args) {MyTimer myTimer = new MyTimer();myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("任务2");}},4000);myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("任务1");}},2000);myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("任务3");}},6000);}
}

但是我们的代码 , 还存在可以优化的地方

2.3 解决 CPU 空转 : wait

image.png

import java.util.concurrent.PriorityBlockingQueue;// 描述任务的类
class MyTask implements Comparable {// 任务要干啥private Runnable command;// 任务什么时候执行// 设置成 public 方便下面访问// 提供他的 public 的 get 方法也可以private long time;public MyTask(Runnable command, long after) {this.command = command;// 任务中需要记录一个完整的时间// 记录到底什么时候(几分几秒)执行// 比如 2023-01-30 09:24this.time = System.currentTimeMillis() + after;}// 执行任务的方法,直接在内部调用 command 的 run 方法public void run() {command.run();}public long getTime() {return time;}@Overridepublic int compareTo(MyTask o) {// 希望时间小的在前面// 试一下就知道了// this.time - o.time 得到的是 long,返回值是int// 所以需要强转return (int) (this.time - o.time);}
}// 自己创建的定时器类
class MyTimer {// 这个是用来阻塞等待的锁对象// 方便下面使用 waitprivate Object locker = new Object();// 使用优先级队列来保存若干个任务private PriorityBlockingQueue queue = new PriorityBlockingQueue<>();// command 代表要执行的任务// after 代表多长时间之后来执行这个任务public void schedule(Runnable command, long after) {// 创建任务MyTask myTask = new MyTask(command, after);// 把任务添加到优先级阻塞队列中queue.put(myTask);synchronized (locker) {locker.notify();}}public MyTimer() {// 在这里启动一个线程Thread t = new Thread(() -> {while (true) {// 循环过程中,不停地从队列中获取到队首元素// 我们首先需要判定队首元素当前时间是否就绪;就绪->执行,不就绪->不执行// 比如这个任务是 12:30 的 , 现在是 12:15 , 就先不执行try {// 把任务取出来MyTask myTask = queue.take();long curTime = System.currentTimeMillis();if (curTime < myTask.getTime()) {// 时间还未到// 再把它放回去queue.put(myTask);synchronized (locker) {// myTask.getTime() - curTime 任务时间-当前时间=等待时间locker.wait(myTask.getTime() - curTime);}} else {// 时间到了// 执行任务myTask.run();}} catch (InterruptedException e) {e.printStackTrace();}}});t.start();}
}public class Demo23 {public static void main(String[] args) {MyTimer myTimer = new MyTimer();myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("任务2");}}, 4000);myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("任务1");}}, 2000);myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("任务3");}}, 6000);}
}

image.png

但是 , 当前代码还是有点问题 …

2.4 防止其他线程插入到入队列和线程等待之间

image.png
出现问题的位置是 put 和 synchronized 之间很有可能被别的线程的任务插入
所以我们把 synchronized 范围扩大 , 把 put 和 wait 打包成一组操作

import java.util.concurrent.PriorityBlockingQueue;// 描述任务的类
class MyTask implements Comparable {// 任务要干啥private Runnable command;// 任务什么时候执行// 设置成 public 方便下面访问// 提供他的 public 的 get 方法也可以private long time;public MyTask(Runnable command, long after) {this.command = command;// 任务中需要记录一个完整的时间// 记录到底什么时候(几分几秒)执行// 比如 2023-01-30 09:24this.time = System.currentTimeMillis() + after;}// 执行任务的方法,直接在内部调用 command 的 run 方法public void run() {command.run();}public long getTime() {return time;}@Overridepublic int compareTo(MyTask o) {// 希望时间小的在前面// 试一下就知道了// this.time - o.time 得到的是 long,返回值是int// 所以需要强转return (int) (this.time - o.time);}
}// 自己创建的定时器类
class MyTimer {// 这个是用来阻塞等待的锁对象// 方便下面使用 waitprivate Object locker = new Object();// 使用优先级队列来保存若干个任务private PriorityBlockingQueue queue = new PriorityBlockingQueue<>();// command 代表要执行的任务// after 代表多长时间之后来执行这个任务public void schedule(Runnable command, long after) {// 创建任务MyTask myTask = new MyTask(command, after);synchronized (locker) {// 把任务添加到优先级阻塞队列中queue.put(myTask);locker.notify();}}public MyTimer() {// 在这里启动一个线程Thread t = new Thread(() -> {while (true) {// 循环过程中,不停地从队列中获取到队首元素// 我们首先需要判定队首元素当前时间是否就绪;就绪->执行,不就绪->不执行// 比如这个任务是 12:30 的 , 现在是 12:15 , 就先不执行try {synchronized (locker) {// 把任务取出来MyTask myTask = queue.take();long curTime = System.currentTimeMillis();if (curTime < myTask.getTime()) {// 时间还未到// 再把它放回去queue.put(myTask);synchronized (locker) {// myTask.getTime() - curTime 任务时间-当前时间=等待时间locker.wait(myTask.getTime() - curTime);}} else {// 时间到了// 执行任务myTask.run();}}} catch (InterruptedException e) {e.printStackTrace();}}});t.start();}
}public class Demo23 {public static void main(String[] args) {MyTimer myTimer = new MyTimer();myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("任务2");}}, 4000);myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("任务1");}}, 2000);myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("任务3");}}, 6000);}
}

image.png

2.5 队列初识为空的情况下 , 需要阻塞等待

image.png

import java.util.concurrent.PriorityBlockingQueue;// 描述任务的类
class MyTask implements Comparable {// 任务要干啥private Runnable command;// 任务什么时候执行// 设置成 public 方便下面访问// 提供他的 public 的 get 方法也可以private long time;public MyTask(Runnable command, long after) {this.command = command;// 任务中需要记录一个完整的时间// 记录到底什么时候(几分几秒)执行// 比如 2023-01-30 09:24this.time = System.currentTimeMillis() + after;}// 执行任务的方法,直接在内部调用 command 的 run 方法public void run() {command.run();}public long getTime() {return time;}@Overridepublic int compareTo(MyTask o) {// 希望时间小的在前面// 试一下就知道了// this.time - o.time 得到的是 long,返回值是int// 所以需要强转return (int) (this.time - o.time);}
}// 自己创建的定时器类
class MyTimer {// 这个是用来阻塞等待的锁对象// 方便下面使用 waitprivate Object locker = new Object();// 使用优先级队列来保存若干个任务private PriorityBlockingQueue queue = new PriorityBlockingQueue<>();// command 代表要执行的任务// after 代表多长时间之后来执行这个任务public void schedule(Runnable command, long after) {// 创建任务MyTask myTask = new MyTask(command, after);synchronized (locker) {// 把任务添加到优先级阻塞队列中queue.put(myTask);locker.notify();}}public MyTimer() {// 在这里启动一个线程Thread t = new Thread(() -> {while (true) {// 循环过程中,不停地从队列中获取到队首元素// 我们首先需要判定队首元素当前时间是否就绪;就绪->执行,不就绪->不执行// 比如这个任务是 12:30 的 , 现在是 12:15 , 就先不执行try {synchronized (locker) {while (queue.isEmpty()) {locker.wait();}// 把任务取出来MyTask myTask = queue.take();long curTime = System.currentTimeMillis();if (curTime < myTask.getTime()) {// 时间还未到// 再把它放回去queue.put(myTask);synchronized (locker) {// myTask.getTime() - curTime 任务时间-当前时间=等待时间locker.wait(myTask.getTime() - curTime);}} else {// 时间到了// 执行任务myTask.run();}}} catch (InterruptedException e) {e.printStackTrace();}}});t.start();}
}public class Demo23 {public static void main(String[] args) {MyTimer myTimer = new MyTimer();myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("任务2");}}, 4000);myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("任务1");}}, 2000);myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("任务3");}}, 6000);}
}

到此为止 , 多线程当中的定时器就给大家讲解完毕了
如果对你有收获的话 , 请一键三连~
在这里插入图片描述

相关内容

热门资讯

教研活动主持词 教研活动主持词  主持人在台上表演的灵魂就表现在主持词中。在当下的中国社会,主持成为很多活动不可或缺...
艺术节主持词开场白 艺术节主持词开场白  什么是艺术节  艺术节是文艺工作者及艺术家、艺术爱好者之间学术交流与学习的重要...
老板在公司年会致辞 老板在公司年会致辞15篇  在平平淡淡的学习、工作、生活中,大家最不陌生的就是致辞了吧,致辞具有针对...
央视春晚主持词台词 央视春晚主持词台词  主持词是主持人在节目进行过程中用于串联节目的串联词。在各种集会、活动不断增多的...
教师节朗诵晚会串词 教师节朗诵晚会串词  主持词需要富有情感,充满热情,才能有效地吸引到观众。在当今不断发展的世界,越来...
趣味运动会主持稿 趣味运动会主持稿(通用7篇)  在充满活力,日益开放的今天,很多地方都会使用到主持稿,主持稿是主持人...
鼠年文艺晚会的主持词 鼠年文艺晚会的主持词  1、喜鹊枝头叫,猴年已来到。好运来报道,健康身边绕。跨羊威武耀,进财装元宝。...
订婚仪式主持词 订婚仪式主持词(通用16篇)  主持词的写作需要将主题贯穿于所有节目之中。在人们积极参与各种活动的今...
文化艺术节闭幕词 文化艺术节闭幕词(精选6篇)  在日新月异的现代社会中,我们都可能会用到闭幕词,闭幕词的作用是辅助讲...
幼儿园签约仪式主持词 幼儿园签约仪式主持词  主持词没有固定的格式,他的最大特点就是富有个性。现今社会在不断向前发展,主持...
电影后来的我们台词 电影后来的我们台词  电影《后来的我们》可以说是风波不断,那么后来的我们经典语录台词有哪些?下面是小...
音乐串词 音乐串词甲:金牛报春来! 又是瑞雪飘飞的季节了,我们打一小学这个大家庭每到这时候总会欢聚一堂。 共庆...
不差钱的经典台词 不差钱的经典台词  赵本山:苏格兰调情。  小沈阳:人家是纯爷们。  丫蛋:洪湖水浪打浪,长江后浪推...
《终结者2》的经典台词 《终结者2》的经典台词  1.I’ll be back。  我会回来的。  2.I need you...
婚宴新娘致辞 婚宴新娘致辞(合集15篇)  无论在学习、工作或是生活中,大家都对致辞很是熟悉吧,在各种重大的庆典、...
单位领导证婚词 单位领导证婚词尊敬的各位来宾、各位亲朋好友,女士们、先生们:大家中--午--好!今天,艳阳高照,天赐...
诵经典唱红歌主持词 诵经典唱红歌主持词  女:各位领导、各位来宾,  男:老师们、同学们,  合:大家好!  女:五月良...
笑傲江湖之东方不败台词 笑傲江湖之东方不败台词大全  天下风云出我辈,  一入江湖岁月催。  皇图霸业谈笑中,  不胜人生一...
学校元宵联欢晚会的主持词 学校元宵联欢晚会的主持词(精选6篇)  利用在中国拥有几千年文化的诗词能够有效提高主持词的感染力。我...
新婚婚礼主持词 新婚婚礼主持词  主持词的写作要突出活动的主旨并贯穿始终。我们眼下的社会,各种集会的节目都通过主持人...