[JAVA] Java定时器Timer的源码分析

2266 0
黑夜隐士 2022-11-8 16:47:38 | 显示全部楼层 |阅读模式
目录

    一、TimerTask
      1. 任务状态2. 任务属性说明3. 任务方法说明
    二、Timer
      1. sched方法2. cancel方法3. purge方法
    三、TaskQueue四、TimerThread

通过源码分析,我们可以更深入的了解其底层原理。
对于JDK自带的定时器,主要涉及TimerTask类、Timer类、TimerQueue类、TimerThread类,其中TimerQueue和TimerThread类与Timer类位于同一个类文件,由Timer内部调用。
先画上一张图,描述一下Timer的大致模型,Timer的模型很容易理解,即任务加入到任务队列中,由任务处理线程循环从任务队列取出任务执行:



一、TimerTask

TimerTask是一个任务抽象类,实现了Runnable接口,是可被线程执行的。

1. 任务状态

在TimerTask中定义了关于任务状态的常量字段:
  1. //        未调度状态
  2. static final intVIRGIN = 0;
  3. //        任务已调度,但未执行
  4. static final int SCHEDULED   = 1;
  5. //        若是一次性任务表示已执行;可重复执行任务,该状态无效
  6. static final int EXECUTED    = 2;
  7. //        任务被取消
  8. static final int CANCELLED   = 3;
复制代码
当一个TimerTask对象创建后,其初始状态为VIRGIN;
当调用Timer的schedule方法调度了此TimerTask对象后,其状态变更为SCHEDULED;
如果TimerTask是一次性任务,此任务执行后,状态将变为EXECUTED,可重复执行任务执行后状态不变;
当中途调用了TimerTask.cancel方法,该任务的状态将变为CANCELLED。

2. 任务属性说明

TimerTask中,有如下成员变量:
  1. //        用于加锁控制多线程修改TimerTask内部状态
  2. final Object lock = new Object();
  3. //        任务状态,初始状态为待未调度状态
  4. int state = VIRGIN;
  5. //        任务的下一次执行时间点
  6. long nextExecutionTime;
  7. //  任务执行的时间间隔。正数表示固定速率;负数表示固定时延;0表示只执行一次
  8. long period = 0;
复制代码
3. 任务方法说明

TimerTask中有三个方法:
    run:实现了Runnable接口,创建TimerTask需要重写此方法,编写任务执行代码cancel:取消任务scheduledExecutionTime:计算执行时间点
3.1. Cancel方法
cancel方法的实现代码:
  1. public boolean cancel() {
  2.     synchronized(lock) {
  3.         boolean result = (state == SCHEDULED);
  4.         state = CANCELLED;
  5.         return result;
  6.     }
  7. }
复制代码
在cancel方法内,使用synchronized加锁,这是因为Timer内部的线程会对TimerTask状态进行修改,而调用cancel方法一般会是另外一个线程。
为了避免线程同步问题,cancel在修改状态前进行了加锁操作。
调用cancel方法将会把任务状态变更为CANCELLED状态,即任务取消状态,并返回一个布尔值,该布尔值表示此任务之前是否已是SCHEDULED 已调度状态。
3.2. scheduledExecutionTime方法
scheduledExecutionTime方法实现:
  1. public long scheduledExecutionTime() {
  2.     synchronized(lock) {
  3.         return (period < 0 ? nextExecutionTime + period
  4.                            : nextExecutionTime - period);
  5.     }
  6. }
复制代码
该方法返回此任务的下次执行时间点。

二、Timer

分析Timer源代码,Timer在内部持有了两个成员变量:
  1. private final TaskQueue queue = new TaskQueue();
  2. private final TimerThread thread = new TimerThread(queue);
复制代码
TaskQueue是任务队列,TimerThread是任务处理线程。

1. sched方法

无论是使用schedule还是scheduleAtFixedRate方法来调度任务,Timer内部最后都是调用sched方法进行处理。
  1. public void schedule(TimerTask task, Date time) {
  2.     sched(task, time.getTime(), 0);        //        一次性任务,period为0
  3. }
  4. public void schedule(TimerTask task, long delay) {
  5.     ...
  6.     sched(task, System.currentTimeMillis()+delay, 0);        //        一次性任务,period为0
  7. }
  8. public void schedule(TimerTask task, long delay, long period) {
  9.     ...
  10.     sched(task, System.currentTimeMillis()+delay, -period);        //        固定延时模式,-period
  11. }
  12. public void schedule(TimerTask task, Date firstTime, long period) {
  13.     ...
  14.     sched(task, firstTime.getTime(), -period);        //        固定延时模式,-period
  15. }
  16. public void scheduleAtFixedRate(TimerTask task, long delay, long period) {
  17.     ...
  18.     sched(task, System.currentTimeMillis()+delay, period);        //        固定速率模式,period为正
  19. }
  20. public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) {
  21.     ...
  22.     sched(task, firstTime.getTime(), period);        //        固定速率模式,period为正
  23. }
复制代码
sched方法核心代码:
  1. private void sched(TimerTask task, long time, long period) {
  2.    ...
  3.     //        加锁,避免外部其他线程同时调用cancel,同时访问queue产生线程同步问题
  4.     synchronized(queue) {
  5.         //        如果线程已终止,抛出异常
  6.         if (!thread.newTasksMayBeScheduled)
  7.             throw new IllegalStateException("Timer already cancelled.");
  8.         //        加锁,避免多线程访问同一个任务产生线程同步问题
  9.         synchronized(task.lock) {
  10.             //        task的状态必须为VIRGIN,否则认为已经加入调度或者已经取消了,避免重复的调度
  11.             if (task.state != TimerTask.VIRGIN)
  12.                 throw new IllegalStateException(
  13.                     "Task already scheduled or cancelled");
  14.             //        设置下次执行时间点
  15.             task.nextExecutionTime = time;
  16.             //        设置时间间隔
  17.             task.period = period;
  18.             //        任务状态变更为已调度
  19.             task.state = TimerTask.SCHEDULED;
  20.         }
  21.         //        将任务添加到队列中
  22.         queue.add(task);
  23.             //        如果此任务是最近的任务,唤醒线程
  24.         if (queue.getMin() == task)
  25.             queue.notify();
  26.     }
  27. }
复制代码
2. cancel方法

cancel方法一般是由外部其他线程调用,而Timer内部的线程也会对任务队列进行操作,因此加锁。
  1. public void cancel() {
  2.     synchronized(queue) {
  3.         //        修改线程的循环执行标志,令线程能够终止
  4.         thread.newTasksMayBeScheduled = false;
  5.         //        清空任务队列
  6.         queue.clear();
  7.         //        唤醒线程
  8.         queue.notify();
  9.     }
  10. }
复制代码
3. purge方法

当通过TimerTask.cancel将任务取消后,Timer的任务队列还引用着此任务,Timer只有到了要执行时才会移除,其他时候并不会自动将此任务移除,需要调用purge方法进行清理。
  1. public int purge() {
  2.      int result = 0;
  3.      synchronized(queue) {
  4.          //        遍历队列,将CANCELLED状态的任务从任务队列中移除
  5.          for (int i = queue.size(); i > 0; i--) {
  6.              if (queue.get(i).state == TimerTask.CANCELLED) {
  7.                  queue.quickRemove(i);
  8.                  result++;
  9.              }
  10.          }
  11.          //        如果移除任务数不为0,触发重新排序
  12.          if (result != 0)
  13.              queue.heapify();
  14.      }
  15.     //        返回移除任务数
  16.      return result;
  17. }
复制代码
三、TaskQueue

TaskQueue是Timer类文件中封装的一个队列数据结构,内部默认是一个长度128的TimerTask数组,当任务加入时,检测到数组将满将会自动扩容1倍,并对数组元素根据下次执行时间nextExecutionTime按时间从近到远进行排序。
  1. void add(TimerTask task) {
  2.     // 检测数组长度,若不够则进行扩容
  3.     if (size + 1 == queue.length)
  4.         queue = Arrays.copyOf(queue, 2*queue.length);
  5.         //        任务入队
  6.     queue[++size] = task;
  7.         //        排序
  8.     fixUp(size);
  9. }
复制代码
fixUp方法实现:
  1. private void fixUp(int k) {
  2.     while (k > 1) {
  3.         int j = k >> 1;
  4.         if (queue[j].nextExecutionTime <= queue[k].nextExecutionTime)
  5.             break;
  6.         TimerTask tmp = queue[j];  queue[j] = queue[k]; queue[k] = tmp;
  7.         k = j;
  8.     }
  9. }
复制代码
TaskQueue中除了fixUp方法外还有一个fixDown方法,这两个其实就是堆排序算法,在算法专题中再进行详细介绍,只要记住他们的任务就是按时间从近到远进行排序,最近的任务排在队首即可。
  1. private void fixDown(int k) {
  2.     int j;
  3.     while ((j = k << 1) <= size && j > 0) {
  4.         if (j < size &&
  5.             queue[j].nextExecutionTime > queue[j+1].nextExecutionTime)
  6.             j++; // j indexes smallest kid
  7.         if (queue[k].nextExecutionTime <= queue[j].nextExecutionTime)
  8.             break;
  9.         TimerTask tmp = queue[j];  queue[j] = queue[k]; queue[k] = tmp;
  10.         k = j;
  11.     }
  12. }
  13. void heapify() {
  14.     for (int i = size/2; i >= 1; i--)
  15.         fixDown(i);
  16. }
复制代码
四、TimerThread

TimerThread的核心代码位于mainLoop方法:
  1. private void mainLoop() {
  2.     //        死循环,从队列取任务执行
  3.     while (true) {
  4.         try {
  5.             TimerTask task;
  6.             boolean taskFired;
  7.             //        对任务队列加锁
  8.             synchronized(queue) {
  9.                 //        如果队列中没有任务,则进入等待,newTasksMayBeScheduled是线程运行标志位,为false时将退出循环
  10.                 while (queue.isEmpty() && newTasksMayBeScheduled)
  11.                     queue.wait();
  12.                 //        如果任务队列是空的还执行到这一步,说明newTasksMayBeScheduled为false,退出循环
  13.                 if (queue.isEmpty())
  14.                     break;
  15.                 long currentTime, executionTime;
  16.                 //        从队列取得最近的任务
  17.                 task = queue.getMin();
  18.                 //        加锁
  19.                 synchronized(task.lock) {
  20.                     //        如果任务状态是已取消,则移除该任务,重新循环取任务
  21.                     if (task.state == TimerTask.CANCELLED) {
  22.                         queue.removeMin();
  23.                         continue;
  24.                     }
  25.                     //        当前时间
  26.                     currentTime = System.currentTimeMillis();
  27.                     //        任务的执行时间点
  28.                     executionTime = task.nextExecutionTime;
  29.                     //        如果执行时间点早于或等于当前时间,即过期/时间到了,则触发任务执行
  30.                     if (taskFired = (executionTime<=currentTime)) {
  31.                         //        如果任务period=0,即一次性任务
  32.                         if (task.period == 0) {
  33.                             //        从队列移除一次性任务
  34.                             queue.removeMin();
  35.                             //        任务状态变更为已执行
  36.                             task.state = TimerTask.EXECUTED;
  37.                         } else {
  38.                             //        可重复执行任务,重新进行调度,period<0是固定时延,period>0是固定速率
  39.                             queue.rescheduleMin(
  40.                               task.period<0 ? currentTime   - task.period        //        计算下次执行时间
  41.                                             : executionTime + task.period);
  42.                         }
  43.                     }
  44.                 }
  45.                 // taskFired为false即任务尚未到执行时间点,进行等待,等待时间是 执行时间点 - 当前时间点
  46.                 if (!taskFired)
  47.                     queue.wait(executionTime - currentTime);
  48.             }
  49.             //        taskFired为true表示已触发,执行任务
  50.             if (taskFired)  
  51.                 task.run();
  52.         } catch(InterruptedException e) {
  53.         }
  54.     }
  55. }
复制代码
以上就是Java定时器Timer的源码分析的详细内容,更多关于Java Timer的资料请关注中国红客联盟其它相关文章!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

中国红客联盟公众号

联系站长QQ:5520533

admin@chnhonker.com
Copyright © 2001-2025 Discuz Team. Powered by Discuz! X3.5 ( 粤ICP备13060014号 )|天天打卡 本站已运行