当前位置: 首页 > 后端技术 > Java

JavaTimer源码分析

时间:2023-04-01 20:26:53 Java

通过源码分析,我们可以更深入的了解它的底层原理。对于JDK自带的定时器,主要涉及TimerTask类、Timer类、TimerQueue类、TimerThread类。TimerQueue和TimerThread类与Timer类位于同一个类文件中,由Timer内部调用。先画一张图,描述一下Timer的大致模型。Timer的模型很容易理解,就是将任务加入到任务队列中,任务处理线程从任务队列中取出任务执行:1.TimerTaskTimerTask是一个抽象的任务类。它实现了Runnable接口,可以被线程执行。1、任务状态在TimerTask中定义了一个关于任务状态的常量字段://UnscheduledstatusstaticfinalintVIRGIN=0;//任务已被调度,但未执行staticfinalintSCHEDULED=1;//如果是一次性任务,表示已经执行完毕;任务可重复执行,状态无效staticfinalintEXECUTED=2;//任务取消staticfinalintCANCELED=3;创建TimerTask对象时,其初始状态为VIRGIN;当调用Timer的schedule方法时,TimerTask对象被调度后,其状态变为SCHEDULED;如果TimerTask是一次性任务,任务执行后状态变为EXECUTED,可重复任务执行后状态不变;中间调用TimerTask.cancel方法时,任务状态会变为CANCELLED。2.Task属性说明在TimerTask中,有如下成员变量://用于锁定和控制多线程修改TimerTask的内部状态finalObjectlock=newObject();//任务状态,初始状态为未调度状态intstate=VIRGIN;//任务下一次执行时间longnextExecutionTime;//任务执行的时间间隔。正数表示固定利率;负数表示固定延迟;0表示只执行一次longperiod=0;3、任务方法说明TimerTask中有3个方法:run:实现了Runnable接口,创建TimerTask需要重写这个方法,写一个任务执行代码cancel:取消任务scheduledExecutionTime:计算执行时间点3.1。cancel方法cancel方法实现代码:publicbooleancancel(){synchronized(lock){booleanresult=(state==SCHEDULED);状态=取消;返回结果;}}在cancel方法中,使用synchronized来加锁,这是因为Timer内部的线程会修改TimerTask的状态,而调用cancel方法一般会是另外一个线程。为了避免线程同步问题,cancel在修改状态之前执行了一个锁操作。调用cancel方法会将任务状态变为CANCELED状态,即任务取消状态,并返回一个布尔值,表示该任务之前是否在SCHEDULED状态被调度过。3.2.scheduledExecutionTime方法scheduledExecutionTime方法实现:publiclongscheduledExecutionTime(){synchronized(lock){return(period<0?nextExecutionTime+period:nextExecutionTime-period);}}该方法返回本次任务的下一次执行时间点。2.Timer分析Timer的源码。Timer内部持有两个成员变量:privatefinalTaskQueuequeue=newTaskQueue();privatefinalTimerThreadthread=newTimerThread(queue);TaskQueue是任务队列,TimerThread是任务处理线程。1、sched方法无论是schedule还是scheduleAtFixedRate方法来调度任务,Timer内部都会调用sched方法进行处理。publicvoidschedule(TimerTasktask,Datetime){sched(task,ti??me.getTime(),0);//一次性任务,周期为0}publicvoidschedule(TimerTasktask,longdelay){...sched(task,System.currentTimeMillis()+delay,0);//一次性任务,周期为0}publicvoidschedule(TimerTasktask,longdelay,longperiod){...sched(task,System.currentTimeMillis()+delay,-period);//固定延迟模式,-period}publicvoidschedule(TimerTasktask,DatefirstTime,longperiod){...sched(task,firstTime.getTime(),-period);//固定延迟模式,-period}publicvoidscheduleAtFixedRate(TimerTasktask,longdelay,longperiod){...sched(task,System.currentTimeMillis()+delay,period);//固定速率模式,周期为正数}publicvoidscheduleAtFixedRate(TimerTasktask,DatefirstTime,longperiod){...sched(task,firstTime.getTime(),period);//固定速率模式,周期为正数}sched方法核心代码:privatevoidsched(TimerTasktask,longtime,longperiod){...//避免外部加锁其他线程同时调用cancel,同时访问queue产生线程同步问题Lock避免多个线程访问同一个任务导致的线程同步问题。TimerTask.VIRGIN)thrownewIllegalStateException("Taskalreadyscheduledorcanceled");//设置下一次执行时间点task.nextExecutionTime=time;//设置时间间隔task.period=period;//任务状态变为定时任务task.state=TimerTask.SCHEDULED;}//将任务加入队列queue.add(task);//如果这个任务是最近的任务,唤醒线程if(queue.getMin()==task)queue.notify();}}2.cancel方法cancel方法一般是被其他外部线程调用的,Timer内部的线程也会对任务队列进行操作,所以加锁publicvoidcancel(){synchronized(queue){//修改线程的循环执行标志,使线程可以终止thread.newTasksMayBeScheduled=false;//清除任务队列queue.clear();//唤醒线程queue.notify();}}3.purge方法通过TimerTask.cancel取消任务后,Timer的任务队列仍然引用这个任务,Timer只会在需要执行的时候被移除,其他时间不会自动移除任务。需要调用purge方法进行清理。publicintpurge(){int结果=0;synchronized(queue){//遍历队列并从任务队列中移除处于CANCELED状态的任务for(inti=queue.size();i>0;i--){if(queue.get(i).state==TimerTask.CANCELLED){queue.quickRemove(i);结果++;}}//如果移除的任务数不为0,则触发重新排序if(result!=0)queue.heapify();}//返回移除的任务数returnresult;}3.TaskQueueTaskQueue是封装在Timer类文件中的队列数据结构。内部默认是一个TimerTask数组,长度为128,当添加任务时,检测到数组快满时,会自动扩容一倍,数组元素会按照next从近到远排序执行时间nextExecutionTime。voidadd(TimerTasktask){//检查数组的长度,如果不够就扩充if(size+1==queue.length)queue=Arrays.copyOf(queue,2*queue.length);//排队任务[++size]=task;//排序fixUp(size);}fixUp方法实现:privatevoidfixUp(intk){while(k>1){intj=k>>1;如果(队列[j].nextExecutionTime<=队列[k].nextExecutionTime)中断;TimerTasktmp=queue[j];队列[j]=队列[k];队列[k]=tmp;k=j;}}TaskQueue中除了fixUp方法还有一个fixDown方法。这两个其实都是堆排序算法,在算法专题中会详细介绍。请记住,他们的任务是按时间从近到远排序的,最近的任务排在队列的最前面。privatevoidfixDown(intk){intj;while((j=k<<1)<=size&&j>0){if(jqueue[j+1].nextExecutionTime)j++;//j索引最小的孩子if(queue[k].nextExecutionTime<=queue[j].nextExecutionTime)break;TimerTasktmp=queue[j];队列[j]=队列[k];队列[k]=tmp;k=j;}}voidheapify(){for(inti=size/2;i>=1;i--)fixDown(i);}四、TimerThreadTimerThread的核心代码位于mainLoop方法中:privatevoidmainLoop(){//无限循环,从队列中执行任务while(true){try{TimerTasktask;布尔任务已触发;//锁定任务队列synchronized(queue){//如果队列中没有任务,则进入Wait,newTasksMayBeScheduled为线程运行标志,为false时退出循环while(queue.isEmpty()&&newTasksMayBeScheduled)queue.wait();//如果任务队列为空,仍然执行到这一步,说明newTasksMayBeScheduled为false,退出循环if(queue.isEmpty())break;长电流时间,执行时间;//从队列中获取最新的任务task=queue.得到最小值();//Locksynchronized(task.lock){//如果任务状态为取消,移除任务并回收任务if(task.state==TimerTask.CANCELLED){queue.removeMin();继续;}//当前时间currentTime=System.currentTimeMillis();//任务的执行时间executionTime=task.nextExecutionTime;//如果执行时间早于或等于当前时间,即expired/时间到了,则触发任务执行if(taskFired=(executionTime<=currentTime)){//如果任务周期=0,则为一次性任务if(task.period==0){//从队列中移除一次性任务queue.removeMin();//任务状态变为已执行task.state=TimerTask.EXECUTED;}else{//任务可以重复执行和重新调度,period<0为固定延迟,period>0为固定速率queue.rescheduleMin(task.period<0?currentTime-task.period//计算第二次执行时间:执行时间+task.period);}}}//taskFired为false,即任务还没有到执行时间点,等待时间为执行时间点-当前时间点if(!taskFired)queue.wait(executionTime-currentTime);}//taskFired为true表示已经触发,执行任务if(taskFired)task.run();}catch(InterruptedExceptione){}}}