大连建设网站公司,线上销售培训班课程,wordpress 代码生成器,网站模板打包关于java标准库中的定时器的使用可以看定时器Timer的使用
大致思路 定义一个MyTimeTask类#xff0c;该类用于组织要执行任务的内容以及执行任务的时间戳#xff0c;后面要根据当前系统时间以及执行任务的时间戳进行比较#xff0c;来判断是否要执行任务或是要等待任务 用一…关于java标准库中的定时器的使用可以看定时器Timer的使用
大致思路 定义一个MyTimeTask类该类用于组织要执行任务的内容以及执行任务的时间戳后面要根据当前系统时间以及执行任务的时间戳进行比较来判断是否要执行任务或是要等待任务 用一个MyTimer类作为定时器的主体在MyTimer类中用小根堆这个数据结构来存储要执行的任务 在MyTimer类中定义一个schedule方法通过传入的参数来实例化一个任务再将这个任务插入到小根堆中 在MyTimer类中创建一个扫描线程一方面监控堆首元素是否到点了一方面在到点后要调用Runable中的run方法来执行任务因为在创建MyTimer对象时就应该开始进行对任务的扫描所以扫描线程应该定义在MyTimer类的构造方法中
为什么要用小根堆来存储待执行的任务呢 因为小根堆的堆顶是等待时间最短的任务在众多任务中我们只需要关注最早要进行的那个任务就可以了要是最早要进行的任务都还不能执行其他任务肯定都不能执行如果我们不用小根堆的话用链表的话我们想要知道接下来要执行的任务就需要一直不停的遍历链表中的任务将执行任务的时间戳与当前时间进行比较一直不停的遍历代表一直占用cpu资源这是不合理的。所以用小根堆来存储待执行的任务呢是比较合理的
MyTimeTask类的创建 代码展示
//描述要执行任务的内容以及执行任务的时间戳
class MyTimeTask implements ComparableMyTimeTask{//由于该类的对象要存放到小根堆中所以要实现Comparable接口重写compareTo方法// 才可以给出合适的比较规则才可以放入堆中Overridepublic int compareTo(MyTimeTask o) {return (int)(this.time-o.time);}//要执行任务的内容private Runnable runnable;//执行任务的时间戳private long time;//构造函数传入要执行的任务以及等待的时间//delay是一个时间差类似于3000这样的数值public MyTimeTask(Runnable runnable,long delay){this.runnablerunnable;timeSystem.currentTimeMillis()delay; //当前系统时间加上延迟的时间就是任务要执行的时间戳}public Runnable getRunnable(){return runnable;}public long getTime(){return time;}
} 注意事项 1.由于MyTimeTask类中的time成员属性代表的是任务执行的时间戳但是用户输入的参数delay是任务执行的延迟时间所以任务执行的时间戳是当前系统时间加上延迟的时间 2.由于MyTimeTask类要添加到小根堆中所以要先在MyTimeTask类中定义好放到堆中的优先级关系这里采用MyTimeTask类实现Comparable接口重写compareTo方法这个方法来规定MyTimeTask类的优先级关系
MyTimer类的创建 代码展示
/定时器类的主体
class MyTimer{//用小根堆这个数据结构来存储要执行的任务//因为小根堆堆顶的数是延迟时间最短最快要执行的任务PriorityQueueMyTimeTask queuenew PriorityQueue();Object lockernew Object(); //用来加锁的锁对象//定时器的核心方法将任务添加到堆中//涉及到多线程可能有多个线程调用schedule添加任务到堆中而且MyTimer内部的线程也要对堆进行修改//所以存在线程安全问题并且schedule和扫描线程应该对同一个对象进行加锁public void schedule(Runnable runnable,long delay){synchronized(locker){MyTimeTask tasknew MyTimeTask(runnable,delay);queue.offer(task);locker.notify();}}//定义一个扫描线程一方面监控堆首元素是否到点了一方面在到点后要调用Runable中的run方法来执行任务//扫描线程不应该是用户自己调用的而是一创建MyTimer对象就要有扫描线程来判断是否有需要执行的任务所以应该写在构造方法中public MyTimer(){Thread tnew Thread(()-{synchronized(locker){ //因为接下来的代码中都涉及到对堆的操作所以直接整体加上锁while (true){//wait推荐和while搭配使用因为wait可能是不是被notify正常唤醒的所以被唤醒了以后条件可能依然不满足// 所以要用while循环对条件是否满足进行多次判断while (queue.isEmpty()){//continue;try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}//取出任务判断是否需要执行不删除MyTimeTask taskqueue.peek();if(task.getTime()System.currentTimeMillis()){task.getRunnable().run(); //时间到了要执行任务queue.poll(); //执行任务后将任务从堆中弹出}else {//任务时间还没有到不需要执行进行睡眠等时间到了再执行try {//sleep不适合在这里用于进行休眠//1.sleep休眠不会释放锁就代表在进行休眠的时候虽然线程没有对堆进行操作// 但locker对象还是被锁着的此时要往队列中添加数据也要进行等待这是不合理的//2.如果我们新增了一个任务可能这条任务的执行时间比当前堆顶最近的执行时间还要早进行//此时我们就需要从睡眠状态脱离去检查是否要更改随眠时间但sleep睡眠的过程中不方便提前中断//虽然可以用interrupt提前中断但是使用interrupt意味着程序已经要结束了//所以使用wait来进行休眠是比sleep更加合理的//Thread.sleep(task.getTime()-System.currentTimeMillis());locker.wait(task.getTime()-System.currentTimeMillis());} catch (InterruptedException e) {throw new RuntimeException(e);}}}}});//线程定义完毕启动线程t.start();}} 注意事项 1.定义一个schedule方法用户通过传入任务执行内容以及延迟时间来使用该方法在schedule中通过用户传入的参数来实例化一个MyTimeTask的任务将该任务添加到小根堆中。 2.要进行执行任务的话要在MyTimer内部的扫描线程中执行由于MyTimer类实例化以后就需要判断是否有新的任务需要处理所以扫描线程应该创建在MyTimer类的构造方法中通过循环扫描堆顶的的任务当堆为空的时候就进入wait睡眠直到用户调用schedule方法向堆中传入任务的时候才调用notify解除睡眠关于wait和notify可以看通过wait和notify来协调线程执行顺序 当堆不为空的时候就要取出堆顶的任务根据执行任务的时间戳以及当期系统时间来判断是否需要执行不删除要是当前系统时间以及任务时间的话就需要调用task.getRunnable().run()来执行任务要是当前系统时间以及任务时间就需要进行wait睡眠睡眠的时间便是任务执行的时间戳-当前系统时间因为当前所扫描的这个任务便是最早执行的了所以其他任务没有扫描的必要它们更完执行 3.为什么要schedule方法和扫描线程加上synchronized锁呢关于synchronized锁可以看线程安全问题因为schedule方法涉及到向堆中添加数据要是多个线程调用schedule方法就会出现线程安全问题而扫描线程中也有多次涉及到堆的使用所以也同样具有线程安全问题所以要加上synchronized锁来保证线程安全并且因为schedule方法和扫描线程都是对堆进行修改所以它们要对同一个对象加锁。 4.sleep同样也可以1进行睡眠为什么我们要用waitnotify呢 1.sleep休眠不会释放锁就代表在进行休眠的时候虽然线程没有对堆进行操作 但locker对象还是被锁着的此时要往队列中添加数据也要进行等待这是不合理的 2.如果我们新增了一个任务可能这条任务的执行时间比当前堆顶最近的执行时间还要早进行,此时我们就需要从睡眠状态脱离去检查是否要更改随眠时间但sleep睡眠的过程中不方便提前中断,虽然可以用interrupt提前中断但是使用interrupt意味着程序已经要结束了 所以使用wait来进行休眠是比sleep更加合理的