zoukankan      html  css  js  c++  java
  • JAVA定时任务Timer

    故事起因

    因业务需要,写了一个定时任务Timer,任务将在每天的凌晨2点执行,代码顺利码完,一切就绪,开始测试。运行程序,为了节省时间,将系统时间调整为第二天凌晨1点59分,看着秒针滴答滴答的转动,期盼着到2点时程序能正确运行,正暗暗欣喜之时,时间滑过2点,但是程序没有任何反应,啊哦,难道是我程序写错了。悲剧。 

    二次测试

    首先检查自己写的程序没有什么问题。再次测试,先将时间调整为1点59分,打上断点,再运行程序,2点到来,程序运行到断点处,一步一步往下走,一切正常。为何,刚刚不是还是不能运行吗。又重复测试了几次,发现一个规律,先调整好时间后再运行程序一切正常,但是先运行程序再调整时间就什么没有任何反应。没办法了,只能研究一下JDK的Timer源码,看看内部有什么玄机。 

    了解内幕

    我们先看看类的关系,见下图:

    图1

    其中Task是我自己写的任务类,这个类需要继承TimerTask,并且实现run()抽象方法,需要将任务执行的相关代码写在run方法中。

    1 public class Task extends TimerTask {
    2     @Override
    3     public void run() {
    4         System.out.println("do task...");
    5     }
    6 }

    执行任务的方法如下:

     1 public class TimerManager {
     2    public TimerManager() {
     3       ……
     4 
     5       Timer timer = new Timer();
     6       Task task = new Task();
     7       // 安排指定的任务在指定的时间开始进行重复的固定延迟执行。
     8       timer.schedule(task, date, ConfigData.getDeltaTime() * 1000);
     9    }
    10 }

    在执行任务的时候,我们只跟Timer打交道,所以先来了解一下Timer.

    Timer的构造函数如下,又调用了自己的另一个构造函数 :

    1 public Timer() {
    2         this("Timer-" + serialNumber());
    3 }
    4 
    5 public Timer(String name) {
    6         thread.setName(name);
    7         thread.start();
    8 }

    到了这一步,我们需要了解thread是个什么玩意儿,我们来看看他的定义:

    1 private TimerThread thread = new TimerThread(queue);

    此时我们需要了解的对象成了TimerThread了,顺藤摸瓜,接着往下看吧: 

     1 class TimerThread extends Thread {
     2     boolean newTasksMayBeScheduled = true;
     3 
     4     private TaskQueue queue;
     5 
     6     TimerThread(TaskQueue queue) {
     7         this.queue = queue;
     8     }
     9 
    10     public void run() {
    11         try {
    12             mainLoop();
    13         } finally {
    14             // Someone killed this Thread, behave as if Timer cancelled
    15             synchronized(queue) {
    16                 newTasksMayBeScheduled = false;
    17                 queue.clear();  // Eliminate obsolete references
    18             }
    19         }
    20     }
    21 
    22     /**
    23      * The main timer loop.  (See class comment.)
    24      */
    25     private void mainLoop() {
    26         while (true) {
    27             try {
    28                 TimerTask task;
    29                 boolean taskFired;
    30                 synchronized(queue) {
    31                     // Wait for queue to become non-empty
    32                     while (queue.isEmpty() && newTasksMayBeScheduled) 
    33                         queue.wait();
    34                     if (queue.isEmpty())
    35                         break; // Queue is empty and will forever remain; die
    36 
    37                     // Queue nonempty; look at first evt and do the right thing
    38                     long currentTime, executionTime;
    39                     task = queue.getMin();
    40                     synchronized(task.lock) {
    41                         if (task.state == TimerTask.CANCELLED) {
    42                             queue.removeMin();
    43                             continue;  // No action required, poll queue again
    44                         }
    45                         currentTime = System.currentTimeMillis();
    46                         executionTime = task.nextExecutionTime;
    47                         if (taskFired = (executionTime<=currentTime)) {
    48                             if (task.period == 0) { // Non-repeating, remove
    49                                 queue.removeMin();
    50                                 task.state = TimerTask.EXECUTED;
    51                             } else { // Repeating task, reschedule
    52                                 queue.rescheduleMin(
    53                                   task.period<0 ? currentTime   - task.period
    54                                                 : executionTime + task.period);
    55                             }
    56                         }
    57                     }
    58                     if (!taskFired) // Task hasn't yet fired; wait
    59                         queue.wait(executionTime - currentTime);
    60                 }
    61                 if (taskFired) // Task fired; run it, holding no locks
    62                     task.run();
    63             } catch(InterruptedException e) {
    64             }
    65         }
    66     }
    67 }

    我们可以看到TimerThread主要就是mianLoop()方法,在mainLoop方法中有这么两行代码:

    1 while (queue.isEmpty() && newTasksMayBeScheduled) 
    2     queue.wait();

    当我们 new Timer(),然后两次调用Timer()的构造函数,并调用thread.start()时,就到了mainLoop方法的这两行代码了,此时的queue.isEmpty()为true,所以线程就wait在这个地方了。什么时候将他notify呢?我们接着往下看我们自己的代码,new Timer(),new Task()之后就到了下面的这行代码了:

    1 timer.schedule(task, date, ConfigData.getDeltaTime() * 1000);

    继续摸索一下timer.schedule()是怎么工作的:

     1 public void schedule(TimerTask task, Date firstTime, long period) {
     2         if (period <= 0)
     3             throw new IllegalArgumentException("Non-positive period.");
     4         sched(task, firstTime.getTime(), -period);
     5 }
     6 
     7      private void sched(TimerTask task, long time, long period) {
     8         if (time < 0)
     9             throw new IllegalArgumentException("Illegal execution time.");
    10 
    11         synchronized(queue) {
    12             if (!thread.newTasksMayBeScheduled)
    13                 throw new IllegalStateException("Timer already cancelled.");
    14 
    15             synchronized(task.lock) {
    16                 if (task.state != TimerTask.VIRGIN)
    17                     throw new IllegalStateException(
    18                         "Task already scheduled or cancelled");
    19                 task.nextExecutionTime = time;
    20                 task.period = period;
    21                 task.state = TimerTask.SCHEDULED;
    22             }
    23 
    24             queue.add(task);
    25             if (queue.getMin() == task)
    26                 queue.notify();
    27         }
    28     }

    当我们跟踪到方法sched时可以看到,这方法中设置了任务的下一次执行时间为传入的时间task.nextExecutionTime = time,然后把添加任务到队列中并notify队列。

    到了这里我们要回到之前的wait位置了:

    1 while (queue.isEmpty() && newTasksMayBeScheduled) 
    2     queue.wait();

    现在queue.isEmpty()为false了,得继续往下运行,

    1 currentTime = System.currentTimeMillis();
    2 executionTime = task.nextExecutionTime;
    3 if (taskFired = (executionTime<=currentTime))

    程序取得当前的时间和任务下一次执行的时间,比较如果执行的时间还未到则任务执行为false,即taskFired=false。

    1 if (!taskFired) // Task hasn't yet fired; wait
    2     queue.wait(executionTime - currentTime);

    所以程序为进入wait状态,要wait多久呢?时间为executionTime – currentTime

    而如果当前执行程序的时间在任务执行的时间之后了,则任务执行为true,即taskFired=true。

    1 if (taskFired) // Task fired; run it, holding no locks
    2     task.run();

    任务立即会被执行。

    豁然开朗

    到这里我们就清楚了为什么之前的测试是那样一个现象,当我们先运行程序再将时间调整为1点59分后,程序一直处于queue.wait(executionTime - currentTime)状态,需要wait的时间为executionTime – currentTime,所以刚过2点时程序是不会马上运行的,必须要等待 executionTime – currentTime的时间后才能执行任务。

  • 相关阅读:
    来自平时工作中的javascript知识的积累---持续补充中
    javascript function
    CSS3 3D变换
    HTTP1.1缓存策略
    jQuery插件开发
    mac下好用的工具收录(慢慢完善)
    mac 彻底卸载vscode
    Git冲突:commit your changes or stash them before you can merge. 解决办法(转载)
    关于vscode使用的一些设置
    (linux服务器)apache开启gzip的配置以及效果对比
  • 原文地址:https://www.cnblogs.com/luochengor/p/3232397.html
Copyright © 2011-2022 走看看