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

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

相关内容

热门资讯

常用商务英语口语   商务英语是以适应职场生活的语言要求为目的,内容涉及到商务活动的方方面面。下面是小编收集的常用商务...
六年级上册英语第一单元练习题   一、根据要求写单词。  1.dry(反义词)__________________  2.writ...
复活节英文怎么说 复活节英文怎么说?复活节的英语翻译是什么?复活节:Easter;"Easter,anniversar...
2008年北京奥运会主题曲 2008年北京奥运会(第29届夏季奥林匹克运动会),2008年8月8日到2008年8月24日在中华人...
英语道歉信 英语道歉信15篇  在日常生活中,道歉信的使用频率越来越高,通过道歉信,我们可以更好地解释事情发生的...
六年级英语专题训练(连词成句... 六年级英语专题训练(连词成句30题)  1. have,playhouse,many,I,toy,i...
上班迟到情况说明英语   每个人都或多或少的迟到过那么几次,因为各种原因,可能生病,可能因为交通堵车,可能是因为天气冷,有...
小学英语教学论文 小学英语教学论文范文  引导语:英语教育一直都是每个家长所器重的,那么有关小学英语教学论文要怎么写呢...
英语口语学习必看的方法技巧 英语口语学习必看的方法技巧如何才能说流利的英语? 说外语时,我们主要应做到四件事:理解、回答、提问、...
四级英语作文选:Birth ... 四级英语作文范文选:Birth controlSince the Chinese Governmen...
金融专业英语面试自我介绍 金融专业英语面试自我介绍3篇  金融专业的学生面试时,面试官要求用英语做自我介绍该怎么说。下面是小编...
我的李老师走了四年级英语日记... 我的李老师走了四年级英语日记带翻译  我上了五个学期的小学却换了六任老师,李老师是带我们班最长的语文...
小学三年级英语日记带翻译捡玉... 小学三年级英语日记带翻译捡玉米  今天,我和妈妈去外婆家,外婆家有刚剥的`玉米棒上带有玉米籽,好大的...
七年级英语优秀教学设计 七年级英语优秀教学设计  作为一位兢兢业业的人民教师,常常要写一份优秀的教学设计,教学设计是把教学原...
我的英语老师作文 我的英语老师作文(通用21篇)  在日常生活或是工作学习中,大家都有写作文的经历,对作文很是熟悉吧,...
英语老师教学经验总结 英语老师教学经验总结(通用19篇)  总结是指社会团体、企业单位和个人对某一阶段的学习、工作或其完成...
初一英语暑假作业答案 初一英语暑假作业答案  英语练习一(基础训练)第一题1.D2.H3.E4.F5.I6.A7.J8.C...
大学生的英语演讲稿 大学生的英语演讲稿范文(精选10篇)  使用正确的写作思路书写演讲稿会更加事半功倍。在现实社会中,越...
VOA美国之音英语学习网址 VOA美国之音英语学习推荐网址 美国之音网站已经成为语言学习最重要的资源站点,在互联网上还有若干网站...
商务英语期末试卷 Part I Term Translation (20%)Section A: Translate ...