本篇文章 , 给大家带来的是多线程当中的定时器 , 定时器是一个线程工具,可以用于调度多个定时任务以后台线程的方式运行。
比如说,在开发中,我们需要做一些周期性的操作,比如每隔三分钟就清空一次文件夹,用定闹钟的方式人为的清空肯定是不合适的。这时就可以通过 Timer 和 TimerTask 类来实现,定时完成具体任务的功能。
大家可以跳转到此页面观看效果更加
上一篇文章的链接也给大家贴在这里了
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);}
}
那这是怎么回事呢 ?
实现定时器 , 背后涉及到多线程 !
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);}}
}
Timer 其实可以往里面加入很多任务的
我们要实现的计时器 , 就需要考虑下面几个因素
虽然这里的任务有很多 , 但是他们的执行顺序是一定的 , 按照时间顺序先后来执行 , 所以我们可以使用优先级队列来组织这些任务 , 时间小的 (time 属性小的) 先执行
但是使用优先级队列还有可能出现问题 :
这个队列会被多个线程同时访问
读和写同步进行 , 就可能会造成线程不安全问题
所以我们选择使用 PriorityBlockingQueue
他既满足了优先级队列的特点 : 先进先出 , 又满足了阻塞队列的特点 : 线程安全
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);}
}
我们运行一下
这是因为我们没定义优先级的比较规则
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);}
}
但是我们的代码 , 还存在可以优化的地方
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);}
}
但是 , 当前代码还是有点问题 …
出现问题的位置是 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);}
}
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);}
}
到此为止 , 多线程当中的定时器就给大家讲解完毕了
如果对你有收获的话 , 请一键三连~
上一篇:数据结构-绪论
下一篇:系统日志错误:7026处理方法