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);}
}

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

相关内容

热门资讯

我生病了小学作文【精简6篇】 我生病了小学作文 篇一我生病了前几天,我不知道怎么了,突然感觉身体不舒服。我感到头晕目眩,喉咙痛得像...
新学期新打算小学作文450字... 新学期新打算篇一:我要努力学习新的学期开始了,我制定了新的打算,那就是要努力学习。我相信只有努力学习...
我学会了西红柿炒鸡蛋小学作文... 我学会了西红柿炒鸡蛋小学作文 篇一我学会了西红柿炒鸡蛋上周,我学会了一道简单又美味的菜——西红柿炒鸡...
花朵的小学作文【最新3篇】 花朵的小学作文 篇一花朵的奇妙世界花朵是大自然的美丽礼物,它们以各种各样的颜色和形状装点着我们的环境...
小学生赏花的作文【通用4篇】 小学生赏花的作文 篇一春天是一个充满美丽花朵的季节,我非常喜欢春天。每当春天来临,我就会和家人一起去...
中秋之夜小学生作文【优选3篇... 中秋之夜小学生作文 篇一中秋之夜,月亮圆圆的,像一块白玉挂在天空中。我和爸爸妈妈一起出门,欣赏美丽的...
油面筋塞肉小学作文(推荐3篇... 油面筋塞肉小学作文 篇一我喜欢吃美食,尤其是一些特色的小吃。最近,我发现了一种非常好吃的小吃,那就是...
学游泳的小学作文(实用3篇) 学游泳的小学作文 篇一学游泳的小学作文大家好!我是小明,今天我要给大家分享一下我学游泳的经历。我是一...
小学生作文老师我想对你说【最... 小学生作文老师我想对你说 篇一尊敬的老师:您好!我是您的学生小明。我想借这篇作文向您表达我的感激之情...
一次有趣的实验小学生作文80... 一次有趣的实验篇一昨天,我参加了一次非常有趣的实验。老师让我们小组一起进行,我非常期待这个实验的结果...
春天小学一年级作文300字【... 春天小学一年级作文300字 篇一我的春天春天来了,大地上百花盛开,绿草如茵。我喜欢春天,因为春天是个...
校园的一角的作文【优选6篇】 校园的一角的作文 篇一校园的一角在校园的一角,有一个小花园,是我最喜欢的地方。虽然它不大,但却别有一...
参观科技馆的小学作文400字... 参观科技馆的小学作文400字 篇一:奇妙的科技世界我参观了我们学校附近的科技馆,这里展示了许多令人惊...
值得的学生作文【实用3篇】 值得的学生作文 篇一突破自我,迈向成功作为一名学生,我们应该时刻保持一种积极向上的心态,勇于追求进步...
走进直播间小学作文(最新4篇... 走进直播间小学作文 篇一近年来,随着互联网技术的快速发展,直播已经成为了一种非常流行的媒体形式。除了...
我的学校小学作文350字【精... 我的学校小学作文350字 篇一我所在的学校是一所小学,位于市区的中心地带。学校占地面积较小,但是设施...
春节大扫除小学作文【精选6篇... 春节大扫除小学作文 篇一:春节大扫除的乐趣春节是中国人最重要的传统节日之一,也是一年中家庭团聚最为频...
抓田螺小学作文(最新5篇) 抓田螺小学作文 篇一我和小伙伴们一起去抓田螺今天,天气晴朗,阳光明媚,我和几个好朋友决定一起去抓田螺...
送别的作文【推荐3篇】 送别的作文 篇一送别的作文 篇一人生中,不论是离别还是告别,都是一种成长的过程。无论是与亲人分离,还...
两个“可怜”作文(最新3篇) 两个“可怜”作文 篇一在生活中,我们常常会遇到一些让人心生怜悯的事情。这些事情或许是因为某些原因而导...