门户网站建设公司教程,全网引擎搜索,wordpress调用上传图片,网络服务属于什么税目目录
#x1f6a9;定时器是什么
#x1f6a9;标准库中的定时器
#x1f6a9;自定义定时器
#x1f388;构造Task类
#x1f4dd;相对时间和绝对时间
#x1f388;构造MyTime类
#x1f4dd;队列空和队列不为空
#x1f4dd;wait(带参)解决消耗资源问题
#…目录
定时器是什么
标准库中的定时器
自定义定时器
构造Task类
相对时间和绝对时间
构造MyTime类
队列空和队列不为空
wait(带参)解决消耗资源问题
为什么使用wait,不使用sleep
为什么使用PriorityQueue(),不使用PriorityBlockingQueue()
测试
带有解释的完整代码 定时器是什么
日常开发常见组件。约定一个时间时间到达之后执行某个代码逻辑。定时器非常常见尤其在进行网络通信的时候。比如网络通信中, 如果对方 500ms 内没有返回数据, 则断开连接尝试重连. 比如一个 Map, 希望里面的某个 key 在 3s 之后过期(自动删除). 类似于这样的场景就需要用到定时器
当客户端发出请求之后等待响应如果服务器迟迟没有响应该怎么办网络世界是很复杂的我们也不知道这个请求有没有发过去响应有没有丢
对于客户端来说不能无限的等需要有一个最大限度到达最大限度的时候是重新发一遍还是彻底放弃还是其他的方式........
在java的标准库中都是有现成的定时器实现的。 标准库中的定时器 标准库中提供了一个 Timer 类. Timer 类的核心方法为 schedule . schedule 包含两个参数. 第一个参数指定即将要执行的任务代码, 第二个参数指定多长时间之后执行 (单位为毫秒). import java.util.Timer;
import java.util.TimerTask;public class Test {public static void main(String[] args) {Timer timernew Timer();timer.schedule(new TimerTask() {Overridepublic void run() {System.out.println(2s后执行);}},2000);System.out.println(开始执行);}
} 这段程序就是创建一个定时器然后提交一个2000s后执行的任务。 此处使用的是匿名内部类的写法继承了TimerTask并且创建出了一个实例。这样的目的是为了重写run通过run描述任务的详细情况。因为TimerTask是个抽象类本身是没有方法的它存在的意义是继承本个案例是实现接口Runnable里面的run方法。通过run方法描述任务的详细情况。 另一个参数是相对时间当前安排的任务啥时候执行此处填写的时间就是以当前时刻为基准往后再推xxx的时间。 主线程执行schedule方法的时候就是把这个任务放到timer对象中了与此同时timer里头也包含了一个线程这个线程叫做“扫描线程”一旦时间到扫描线程就会执行刚才安排的任务了。仔细观察可以发现整个进程其实并没有结束就是因为Timer内部的线程阻止了进程结束。等下面实现定时器的时候我们就可以知道为什么不结束了。 自定义定时器 我们自己实现一个定时器的前提是我们需要弄清楚定时器都有什么: Timer timernew Timer(); Timer中需要有一个线程扫描任务是否到时间可以执行了。需要有一个数据结构把所有的任务都保存起来。还需要创建一个类通过类的对象来描述一个任务至少要包含任务内容和时间 创建一个扫描线程相对比较简单我们需要确定一个数据结构来保存我们提交的任务我们提交过来的任务是由任务和时间组成的我们需要构建一个TimeTask对象数据结构我们这里使用优先级队列因为我们的任务是有时间顺序的具有一个优先级并且要保证在多线程下是安全的所以我们这里使用:PriorityQueue比较合适。因为Timer中添加的这些任务都是带有一个“时间”一定是时间小的先执行最先执行的就是时间最小的任务如果时间最小的任务还没到时间其他任务更不会执行了。优先级队列可以使用O(1)时间来获取到时间最小的任务。 构造Task类 MyTask 类用于描述一个任务 ( 作为 Timer 的内部类 ). 里面包含一个 Runnable 对象和一个 time( 毫秒时间戳) 相对时间和绝对时间 //执行任务的时间绝对时间
private long time; 此时记录的是一个“绝对的时间(完整的时间戳。 绝对时间当前具体的时间相对时间时间间隔 schedule方法里面的第二个参数是相对时间为什么构造的时候记录绝对时间呢 后续扫描线程的时候如何判定当前这个任务是否要执行 获取到当前的时间戳 1400再获取到任务要执行的时间戳 1405对比俩个时间戳时间没有到不能执行 当前的时间戳是利用到 System.currentTimeMillis()方法现在我写的时间是20:02此时时间戳转换成时间就是绝对时间。我们再扫描线程的时候我们比较的是 当前的时间戳和我要执行的时候的绝对时间所以我们最好是记录绝对时间。 public MyTask(Runnable runnable,long delay){this.timeSystem.currentTimeMillis()delay;this.runnablerunnable;} 构造方法中的俩个参数是 执行任务的对象以及相对时间。因为再调用schedule方法的时候传的第二个参数是相对时间。我们只需要将 相对时间时间戳绝对时间就可以算出来。 比如现在是20:07的时候调用的是schedule方法执行delay是5分钟那么再对应上面的 System.currentTimeMillis()就是14:00,delay是5分钟那么绝对时间也就是要执行的时间是14:05分。 当我们创建好属性和获取当前的对象和时间的时候我们再代码中还有一定的问题。 我们要想到我们用优先级队列来保存任务那么用到优先级队列要求里面的元素务必是可以比较的。所以我们需要实现Comparable接口继承compareTo方法。 Overridepublic int compareTo(MyTask o) {return (int)(this.time-o.time);} 由于返回类型是int类型而时间戳返回类型是long类型所以我们需要强制类型转换。 我们所创建的任务类里面的成员属性任务对象和绝对时间还有构造方法以及比较方法。为下面的扫描线程铺垫。 构造MyTime类 实现计时器首先我们要创建一个优先级队列里面包含的元素是执行的任务然后实现schedule方法再调用schedule方法的时候就插入队列中而里面的扫描线程应该放在构造方法中因为我们再创建MyTime类的时候线程就启动。 class MyTime{PriorityQueueMyTaskqueuenew PriorityQueue();public void schedule(Runnable runnable,long delay){queue.offer(new MyTask(runnable,delay));//插入队列中}public MyTime(){Thread threadnew Thread(()-{while(true){}});thread.start();//放在构造方法中再创建对象的时候就启用线程。}
} 继续完善代码 队列空和队列不为空 如果队列是空的话我们就要阻塞等待等到再调用schedule后插入任务的时候我们就可以继续执行。我们就会联想到wait()和notify()方法。要想要用wait()就需要加锁。但是我们再上一篇的 阻塞队列中我们知道 wait()再执行的时候要经过三个步骤。 释放锁 ——》前提是先拿到锁等待通知通知到来之后唤醒重新获得锁 其实我们可以看到我们再创建对象的时候扫描线程启动当我们调用schedule方法的时候这也是一个线程再给队列添加元素不同的线程再对同一个队列操作是肯定有线程安全问题的所以不管使不使用wait()都是得加锁的。 public void schedule(Runnable runnable,long delay){synchronized (this){queue.offer(new MyTask(runnable,delay));this.notify();//当插入任务后我们就要唤醒线程}}public MyTime(){Thread threadnew Thread(()-{while(true){try {synchronized (this){while(queue.isEmpty()){this.wait();//如果队列为空我们就要阻塞等待(队列插入任务)}}}catch (InterruptedException e){e.printStackTrace();}}});thread.start();} 如果队列不为空的情况下 首先我们需要获取当前堆顶的元素(也就是执行时间最先的一个然后我们需要获取当前的绝对时间(就是你现在再看我的博客的时间我们需要和任务类里面的time绝对时间这里的绝对时间是我们要执行任务的时间相比较如果当前时间大于等于执行任务时间那么我们就得执行该任务执行完后从队列中删除如果当前时间小于要执行任务的时间我们就得不执行等到时间到了为止。 class MyTime{PriorityQueueMyTaskqueuenew PriorityQueue();public void schedule(Runnable runnable,long delay){synchronized (this){queue.offer(new MyTask(runnable,delay));this.notify();}}public MyTime(){Thread threadnew Thread(()-{while(true){try {synchronized (this){while(queue.isEmpty()){this.wait();}MyTask myTaskqueue.peek();//获取最先执行的任务(里面有任务对象和绝对时间long currentTimeSystem.currentTimeMillis();//获得当前时间if(currentTimemyTask.getTime()){myTask.getRunnable().run();//执行这个任务queue.poll();//删除堆顶元素}else{}}}catch (InterruptedException e){e.printStackTrace();}}});thread.start();}
} wait(带参)解决消耗资源问题 程序到这里还是有点问题的。 这个程序到这里比如我队列中有一个任务是1430执行当时时刻是14:00时间未到当时间没到的时候此处的循环会快速循环很多次相当于时间没到但是我在这不停的看表本来这个时间是可以休息的但是这个不停看表的动作使我并没有休息~同时也没有干活。忙等确实再等但是也消耗了很多cpu资源因为没有到时间依旧进入循环。所以此处也加个wait这里的wait引入带参数的版本带有超时时间把时间间隔作为wait的等待时间了14:00——1430 此时wait就直接等30min就可以了。 this.wait(myTask.getTime()-System.currentTimeMillis());//执行任务时间-当前时间 而且当任务时间没到的时候就wiat阻塞线程不会再cpu上调度也就把cpu资源让出来给别人了 为什么使用wait,不使用sleep 但是为啥使用wait不使用sleep行不行 ——wait是更好的 可能在等待过程中主线程调用schedule添加一个新的任务新的任务是14:10执行比刚才最早的任务还早恰好使用刚才的schedule中的notify就可以唤醒这里的wait让循环再执行一遍重新拿到队首的元素(14:10),接下来wait的时间就更新成10min。 if(currentTimemyTask.getTime()){myTask.getRunnable().run();//执行这个任务queue.poll();//队列中执行完了删除堆顶元素}else{//当前时间还没到任务时间暂时不执行任务但是先啥都不干等待下一轮的循环判定this.wait(myTask.getTime()-System.currentTimeMillis());//执行任务时间-当前时间} 为什么使用PriorityQueue(),不使用PriorityBlockingQueue() 为什么使用priorityQueue()其实就是因为要处理俩个wait地方如果使用阻塞版本的优先级队列不方便实现这样的俩处等待。 用priorityQueue优先级队列时一个notify()可以用到俩个wait()入完任务之后队列不为空就唤醒当最新任务时间还没到的时候进入阻塞新的任务来了之后就唤醒一下然后重新判定一下最早的任务是啥以及更新等待时间。所以notify可以同时唤醒俩个wait()因为入完队列俩个都得唤醒 测试 public class Test_Timer2 {public static void main(String[] args) {MyTimer myTimernew MyTimer();myTimer.schedule(new Runnable() {Overridepublic void run() {System.out.println(chenle);}},2000);myTimer.schedule(new Runnable() {Overridepublic void run() {System.out.println(zyf);}},3000);System.out.println(开始);}
} 因为没有任务的时候代码会一直等待阻塞。 带有解释的完整代码 import java.util.PriorityQueue;//首先创建一个类里面有对象和时间
class MyTask implements ComparableMyTask{private Runnable runnable;//执行任务的时间绝对时间private long time;public MyTask(Runnable runnable,long delay){this.timeSystem.currentTimeMillis()delay;this.runnablerunnable;}public Runnable getRunnable() {return runnable;}public long getTime() {return time;}Overridepublic int compareTo(MyTask o) {return (int)(this.time-o.time);}
}class MyTime{PriorityQueueMyTaskqueuenew PriorityQueue();public void schedule(Runnable runnable,long delay){synchronized (this){queue.offer(new MyTask(runnable,delay));this.notify();}}public MyTime(){//扫描线程需要不停的扫描是否有任务没有完成Thread threadnew Thread(()-{while(true){try {synchronized (this){//不要使用if作为wait的判断条件应该使用while//使用while的目的是为了在wait唤醒的时候再确认一下条件if(queue.isEmpty()){//使用wait进行等待//这里的wait需要有另外的线程唤醒//添加了新的任务就应该唤醒this.wait();}MyTask myTaskqueue.peek();//获取最先执行的任务(里面有任务对象和绝对时间long currentTimeSystem.currentTimeMillis();//获得当前时间//比较一下看当前的队首元素是否可以执行了if(currentTimemyTask.getTime()){myTask.getRunnable().run();//执行这个任务queue.poll();//队列中执行完了删除堆顶元素}else{//当前时间还没到任务时间暂时不执行任务但是先啥都不干等待下一轮的循环判定this.wait(myTask.getTime()-System.currentTimeMillis());//执行任务时间-当前时间}}}catch (InterruptedException e){e.printStackTrace();}}});thread.start();}
}
public class Test_Timer2 {public static void main(String[] args) {MyTimer myTimernew MyTimer();myTimer.schedule(new Runnable() {Overridepublic void run() {System.out.println(chenle);}},2000);myTimer.schedule(new Runnable() {Overridepublic void run() {System.out.println(zyf);}},3000);System.out.println(开始);}
} 如果你不在乎别人的态度那么你的幸福就会变得无比简单。