珠海网站制作推广,怎么提高网站的知名度,3免费网站建站,网站建设与开发专业目录
前言
什么是定时器
如何使用java中的定时器
实现计时器
实现MyTimeTask类
Time类中存储任务的数据结构
实现Timer中的schedule方法 实现MyTimer中的构造方法
处理构造方法中出现的线程安全问题
完整代码 考虑在限时等待wait中能否用sleep替换
能否用PriorityBlo…目录
前言
什么是定时器
如何使用java中的定时器
实现计时器
实现MyTimeTask类
Time类中存储任务的数据结构
实现Timer中的schedule方法 实现MyTimer中的构造方法
处理构造方法中出现的线程安全问题
完整代码 考虑在限时等待wait中能否用sleep替换
能否用PriorityBlockingQueue进行存储 在前面已经讲解了几种常见设计模式那么今天我们就来讲解一下定时器。
前言
在发送信息的时候有时候不想要信息那么快就发送出去而是在特定的时间再发送或者我们在发送邮件时当达到特定的时间时就会自动发送电子邮件给用户那么这里就需要用到定时器那么定时器是什么呢
什么是定时器
定时器是软件开发中的一个重要组件类似于“闹钟”能够在某个特定的时间执行一个或者多个任务定时器是多线程中的一个案例也是一个比较复杂且重要的案例。 如何使用java中的定时器
在java中给我们提供了实现了的定时器包我们可以直接使用。
java中给我们提供的定时器是Timer我们在设置定时任务时需要用到其中的schedule方法。 schedule包含两个参数 第⼀个参数指定即将要执⾏的任务代码 第⼆个参数指定多⻓时间之后 执⾏(单位为毫秒) 示例
class Demos{public static void main(String[] args) {// 创建一个Timer对象用于调度定时任务Timer timernew Timer();// 调度第一个定时任务1秒后执行timer.schedule(new TimerTask() {Overridepublic void run() {System.out.println(hello);}},1000);// 调度第二个定时任务2秒后执行timer.schedule(new TimerTask() {Overridepublic void run() {System.out.println(hello);}},2000);// 调度第三个定时任务3秒后执行timer.schedule(new TimerTask() {Overridepublic void run() {System.out.println(hello);}},3000);}}实现计时器
我们从上述代码中可以看出要实现一个定时器需要实现以下 实现一个任务Task类实现一个Timer类用来存放任务 实现MyTimeTask类
/*** TimeTask类用于封装一个延迟执行的任务* 它包含一个需要执行的任务和一个延迟时间*/
class TimeTask{//需要执行的任务private Runnable runnable;//任务等待执行的时间点以毫秒为单位private long time;/*** 构造函数用于创建一个TimeTask对象* param runnable 需要延迟执行的任务类型为Runnable* param delay 任务延迟执行的时间单位为毫秒*/public TimeTask(Runnable runnable,long delay){this.runnablerunnable;//计算任务应该执行的时间点this.timeSystem.currentTimeMillis()delay;}/*** 返回任务的等待执行时间* return 任务等待执行的时间点以毫秒为单位*/public long getTime(){return this.time;}/*** 执行当前任务* 调用构造时传入的Runnable对象的run方法来执行任务*/public void run(){this.runnable.run();}
}Time类中存储任务的数据结构
我们在存储任务的时候需要根据等待时间来存储时间短的优先取出来那么我们就可以使用优先级队列创建一个小根堆时间最短的放在堆顶。 private PriorityQueueMyTimeTask pqnew PriorityQueue();但是我们这里要怎么比较呢我们可以实现Comparable接口重写compareTo方法来进行比较或者创建一个类来实现Comparator接口重写compare方法来进行比较。
实现Timer中的schedule方法
当我们解决了在任务在优先级队列中如何进行比较存储任务的问题之后那么就可以在MyTimer中来实现schedule方法。根据schedule方法的参数创建一个MyTimeTask类并将其添加到优先级队列中。
/*** 将一个 Runnable 任务安排在指定的延迟时间后执行* * param runnable 要执行的任务* param delay 相对于现在的时间延迟单位为毫秒* * 注意这个方法使用一个优先队列pq来管理这些被安排的任务确保它们在指定的延迟后被执行*/
public void schedule(Runnable runnable, long delay) {// 创建一个 MyTimeTask 对象它包含了 Runnable 任务和延迟时间MyTimeTask myTimeTask new MyTimeTask(runnable, delay);// 将 MyTimeTask 对象添加到优先队列 pq 中以便在未来的某个时间执行pq.add(myTimeTask);
}实现MyTimer中的构造方法
通过实例化一个线程在这个线程中通过多次扫描优先级队列中的元素判断堆顶元素是否到达了等待时长若是则取出并执行。
注意这里不能直接poll取出栈顶元素若栈顶的任务等待时间还未到达则继续循环。 /*** 构造函数初始化MyTimer对象* 创建并启动一个线程用于持续检查并执行已到达设定时间的任务*/public MyTimer(){// 创建一个新的线程来执行定时任务Thread tnew Thread(()-{// 无限循环持续检查任务队列while(true) {if(pq.isEmpty()){continue;}// 查看在栈顶的任务MyTimeTask task pq.peek();// 比较栈顶的任务与当前时间点的比较if (System.currentTimeMillis() task.getTime()) {// 当前时间已达到任务设定时间移除任务并执行pq.poll();task.run();}else {// 如果栈顶的任务时间大于当前时间则继续循环// 继续检查下一个任务或在没有到达时间的任务时继续等待continue;}}});// 启动线程t.start();}
处理构造方法中出现的线程安全问题
在上述代码中能看出哪里存在线程安全问题吗
优先级队列并不是一个线程安全的队列我们在瞥peek取poll的时候可能会出现线程安全问题若是在多线程环境中当线程1刚peek了堆顶任务但此时切换到线程2线程2同样peek堆顶任务并刚好到了等待时间此时就会执行并且删除栈顶任务。此时又切换到线程1但此时线程1peek的堆顶任务已经被poll掉了此时如果再执行就会再次删除堆顶任务导致出现线程安全问题。
所以这里我们需要对peek和poll操作进行加锁。 /*** 构造函数初始化MyTimer对象* 创建并启动一个线程用于持续检查并执行已到达设定时间的任务*/public MyTimer(){// 创建一个新的线程来执行定时任务Thread tnew Thread(()-{// 无限循环持续检查任务队列while(true) {synchronized (lock) {// 如果任务队列为空则跳过当前循环if (pq.isEmpty()) {continue;}// 查看在栈顶的任务MyTimeTask task pq.peek();// 比较栈顶的任务与当前时间点的比较if (System.currentTimeMillis() task.getTime()) {// 当前时间已达到任务设定时间移除任务并执行pq.poll();task.run();} else {// 如果栈顶的任务时间大于当前时间则继续循环// 继续检查下一个任务或在没有到达时间的任务时继续等待continue;}}}});// 启动线程t.start();} 这里还有什么能优化的吗
我们可以看到当优先级队列中不为空但此时堆顶任务的等待时间还没到此时就会进入else分支执行continue但一直重复这样操作可能会造成不断检查cpu使用率过高。那么我们就可以使用带参数的wait来进行限时等待当达到时限时会自动唤醒线程。
同理的在判断队列是否为空时我们可以设置不带参数的wait等待唤醒。 /*** 构造函数初始化MyTimer对象* 创建并启动一个线程用于持续检查并执行已到达设定时间的任务*/public MyTimer(){// 创建一个新的线程来执行定时任务Thread tnew Thread(()-{// 无限循环持续检查任务队列while(true) {synchronized (lock) {// 如果任务队列为空则跳过当前循环while (pq.isEmpty()) {try {lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}// 查看在栈顶的任务MyTimeTask task pq.peek();// 比较栈顶的任务与当前时间点的比较if (System.currentTimeMillis() task.getTime()) {// 当前时间已达到任务设定时间移除任务并执行pq.poll();task.run();} else {// 如果栈顶的任务时间大于当前时间则继续循环try {lock.wait(task.getTime()-System.currentTimeMillis());} catch (InterruptedException e) {throw new RuntimeException(e);}}}}});// 启动线程t.start();}
既然这里等待那么我们就需要有人来唤醒wait所以我们在schedule方法中也需要进行加锁并且在添加完任务后调用notify来进行通知。
/*** 将一个 Runnable 任务安排在指定的延迟时间后执行** param runnable 要执行的任务* param delay 相对于现在的时间延迟单位为毫秒** 注意这个方法使用一个优先队列pq来管理这些被安排的任务确保它们在指定的延迟后被执行*/public void schedule(Runnable runnable, long delay) {synchronized (lock) {// 创建一个 MyTimeTask 对象它包含了 Runnable 任务和延迟时间MyTimeTask myTimeTask new MyTimeTask(runnable, delay);// 将 MyTimeTask 对象添加到优先队列 pq 中以便在未来的某个时间执行pq.add(myTimeTask);// 唤醒可能在等待执行任务的线程lock.notify();}}MyTimer优化到这里其实已经优化好了。
完整代码
package Threads;import java.util.Comparator;
import java.util.PriorityQueue;/*** TimeTask类用于封装一个延迟执行的任务* 它包含一个需要执行的任务和一个延迟时间*/
class TimeTask implements ComparableTimeTask{//需要执行的任务private Runnable runnable;//任务等待执行的时间点以毫秒为单位private long time;/*** 构造函数用于创建一个TimeTask对象* param runnable 需要延迟执行的任务类型为Runnable* param delay 任务延迟执行的时间单位为毫秒*/public TimeTask(Runnable runnable,long delay){this.runnablerunnable;//计算任务应该执行的时间点this.timeSystem.currentTimeMillis()delay;}/*** 返回任务的等待执行时间* return 任务等待执行的时间点以毫秒为单位*/public long getTime(){return this.time;}/*** 执行当前任务* 调用构造时传入的Runnable对象的run方法来执行任务*/public void run(){this.runnable.run();}Overridepublic int compareTo(TimeTask o) {return (int) (this.time- o.getTime());}
}
class MyTimer{//private PriorityQueueMyTimeTask pqnew PriorityQueue(new compareTimeTask());private PriorityQueueMyTimeTask pqnew PriorityQueue();static Object locknew Object();/*** 构造函数初始化MyTimer对象* 创建并启动一个线程用于持续检查并执行已到达设定时间的任务*/public MyTimer(){// 创建一个新的线程来执行定时任务Thread tnew Thread(()-{// 无限循环持续检查任务队列while(true) {synchronized (lock) {// 如果任务队列为空则跳过当前循环while (pq.isEmpty()) {try {lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}// 查看在栈顶的任务MyTimeTask task pq.peek();// 比较栈顶的任务与当前时间点的比较if (System.currentTimeMillis() task.getTime()) {// 当前时间已达到任务设定时间移除任务并执行pq.poll();task.run();} else {// 如果栈顶的任务时间大于当前时间则继续循环try {lock.wait(task.getTime()-System.currentTimeMillis());} catch (InterruptedException e) {throw new RuntimeException(e);}}}}});// 启动线程t.start();}/*** 将一个 Runnable 任务安排在指定的延迟时间后执行** param runnable 要执行的任务* param delay 相对于现在的时间延迟单位为毫秒** 注意这个方法使用一个优先队列pq来管理这些被安排的任务确保它们在指定的延迟后被执行*/public void schedule(Runnable runnable, long delay) {synchronized (lock) {// 创建一个 MyTimeTask 对象它包含了 Runnable 任务和延迟时间MyTimeTask myTimeTask new MyTimeTask(runnable, delay);// 将 MyTimeTask 对象添加到优先队列 pq 中以便在未来的某个时间执行pq.add(myTimeTask);// 唤醒可能在等待执行任务的线程lock.notify();}}}
class Demo{public static void main(String[] args) {MyTimer timernew MyTimer();timer.schedule(()-{System.out.println(Hello World1);},1000);timer.schedule(()-{System.out.println(Hello World2);},2000);timer.schedule(()-{System.out.println(Hello World3);},3000);}
}
class compareTimeTask implements ComparatorMyTimeTask{Overridepublic int compare(MyTimeTask o1, MyTimeTask o2) {return (int) (o1.getTime()-o2.getTime());}
}
测试一下 考虑在限时等待wait中能否用sleep替换 Thread.sleep(task.getTime()-System.currentTimeMillis());这里为什么不用sleep呢
在前面线程安全问题中已经讲解了wait和sleep的区别在这里如果我们使用sleep会导致拉着锁一起进入睡眠导致其他线程拿不到锁对象无法进行加锁。
这会导致我们想要调用schedule方法添加任务时拿不到锁对象。
能否用PriorityBlockingQueue进行存储
在前面我们用的是优先级队列PriorityBlockingQueue来存储任务但如果我们用PriorityBlockingQueue呢
如果我们使用PriorityBlockingQueue那么我们的方法也需要改成take取和put存才能用阻塞等待的效果。
修改代码
package Threads;import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.concurrent.PriorityBlockingQueue;/*** TimeTask类用于封装一个延迟执行的任务* 它包含一个需要执行的任务和一个延迟时间*/
class TimeTask implements ComparableTimeTask{//需要执行的任务private Runnable runnable;//任务等待执行的时间点以毫秒为单位private long time;/*** 构造函数用于创建一个TimeTask对象* param runnable 需要延迟执行的任务类型为Runnable* param delay 任务延迟执行的时间单位为毫秒*/public TimeTask(Runnable runnable,long delay){this.runnablerunnable;//计算任务应该执行的时间点this.timeSystem.currentTimeMillis()delay;}/*** 返回任务的等待执行时间* return 任务等待执行的时间点以毫秒为单位*/public long getTime(){return this.time;}/*** 执行当前任务* 调用构造时传入的Runnable对象的run方法来执行任务*/public void run(){this.runnable.run();}Overridepublic int compareTo(TimeTask o) {return (int) (this.time- o.getTime());}
}
class MyTimer{//private PriorityQueueMyTimeTask pqnew PriorityQueue(new compareTimeTask());private PriorityBlockingQueueMyTimeTask pqnew PriorityBlockingQueue(100);static Object locknew Object();/*** 构造函数初始化MyTimer对象* 创建并启动一个线程用于持续检查并执行已到达设定时间的任务*/public MyTimer(){// 创建一个新的线程来执行定时任务Thread tnew Thread(()-{// 无限循环持续检查任务队列while(true) {synchronized (lock) {// 如果任务队列为空则跳过当前循环while (pq.isEmpty()) {try {lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}// 查看在栈顶的任务MyTimeTask task null;try {task pq.take();} catch (InterruptedException e) {throw new RuntimeException(e);}// 比较栈顶的任务与当前时间点的比较if (System.currentTimeMillis() task.getTime()) {// 当前时间已达到任务设定时间移除任务并执行task.run();} else {// 如果栈顶的任务时间大于当前时间则继续循环pq.put(task);try {lock.wait(task.getTime()-System.currentTimeMillis());} catch (InterruptedException e) {throw new RuntimeException(e);}}}}});// 启动线程t.start();}/*** 将一个 Runnable 任务安排在指定的延迟时间后执行** param runnable 要执行的任务* param delay 相对于现在的时间延迟单位为毫秒** 注意这个方法使用一个优先队列pq来管理这些被安排的任务确保它们在指定的延迟后被执行*/public void schedule(Runnable runnable, long delay) {synchronized (lock) {// 创建一个 MyTimeTask 对象它包含了 Runnable 任务和延迟时间MyTimeTask myTimeTask new MyTimeTask(runnable, delay);// 将 MyTimeTask 对象添加到优先队列 pq 中以便在未来的某个时间执行pq.put(myTimeTask);// 唤醒可能在等待执行任务的线程lock.notify();}}}
class Demo{public static void main(String[] args) {MyTimer timernew MyTimer();timer.schedule(()-{System.out.println(Hello World1);},1000);timer.schedule(()-{System.out.println(Hello World2);},2000);timer.schedule(()-{System.out.println(Hello World3);},3000);}
}
class compareTimeTask implements ComparatorMyTimeTask{Overridepublic int compare(MyTimeTask o1, MyTimeTask o2) {return (int) (o1.getTime()-o2.getTime());}
}
由于take会触发阻塞等待而后面的wait也会这里加了两次锁容易引出线程安全问题所以我们建议使用一个锁对象lock来进行加锁就行。而不使用无界阻塞队列。 以上就是本篇所有内容~
若有不足欢迎指正~