乐趣区

关于java:Java-Timer源码分析

通过源码剖析,咱们能够更深刻的理解其底层原理。
对于 JDK 自带的定时器,次要波及 TimerTask 类、Timer 类、TimerQueue 类、TimerThread 类,其中 TimerQueue 和 TimerThread 类与 Timer 类位于同一个类文件,由 Timer 外部调用。

先画上一张图,形容一下 Timer 的大抵模型,Timer 的模型很容易了解,即工作退出到工作队列中,由工作解决线程循环从工作队列取出工作执行:

一、TimerTask

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

1. 工作状态

在 TimerTask 中定义了对于工作状态的常量字段:

//    未调度状态
static final int VIRGIN = 0;
//    工作已调度,但未执行
static final int SCHEDULED   = 1;
//    若是一次性工作示意已执行;可反复执行工作,该状态有效
static final int EXECUTED    = 2;
//    工作被勾销
static final int CANCELLED   = 3;

当一个 TimerTask 对象创立后,其初始状态为 VIRGIN;
当调用 Timer 的 schedule 办法调度了此 TimerTask 对象后,其状态变更为 SCHEDULED;
如果 TimerTask 是一次性工作,此工作执行后,状态将变为 EXECUTED,可反复执行工作执行后状态不变;
当中途调用了 TimerTask.cancel 办法,该工作的状态将变为 CANCELLED。

2. 工作属性阐明

TimerTask 中,有如下成员变量:

//    用于加锁管制多线程批改 TimerTask 外部状态
final Object lock = new Object();

//    工作状态,初始状态为待未调度状态
int state = VIRGIN;

//    工作的下一次执行工夫点
long nextExecutionTime;

//  工作执行的工夫距离。负数示意固定速率;正数示意固定时延;0 示意只执行一次
long period = 0;

3. 工作办法阐明

TimerTask 中有三个办法:

  • run:实现了 Runnable 接口,创立 TimerTask 须要重写此办法,编写工作执行代码
  • cancel:勾销工作
  • scheduledExecutionTime:计算执行工夫点

3.1. Cancel 办法

cancel 办法的实现代码:

public boolean cancel() {synchronized(lock) {boolean result = (state == SCHEDULED);
        state = CANCELLED;
        return result;
    }
}

在 cancel 办法内,应用 synchronized 加锁,这是因为 Timer 外部的线程会对 TimerTask 状态进行批改,而调用 cancel 办法个别会是另外一个线程。
为了防止线程同步问题,cancel 在批改状态前进行了加锁操作。
调用 cancel 办法将会把工作状态变更为 CANCELLED 状态,即工作勾销状态,并返回一个布尔值,该布尔值示意此工作之前是否已是 SCHEDULED 已调度状态。

3.2. scheduledExecutionTime 办法

scheduledExecutionTime 办法实现:

public long scheduledExecutionTime() {synchronized(lock) {
        return (period < 0 ? nextExecutionTime + period
                           : nextExecutionTime - period);
    }
}

该办法返回此工作的下次执行工夫点。

二、Timer

剖析 Timer 源代码,Timer 在外部持有了两个成员变量:

private final TaskQueue queue = new TaskQueue();

private final TimerThread thread = new TimerThread(queue);

TaskQueue 是工作队列,TimerThread 是工作解决线程。

1. sched 办法

无论是应用 schedule 还是 scheduleAtFixedRate 办法来调度工作,Timer 外部最初都是调用 sched 办法进行解决。

public void schedule(TimerTask task, Date time) {sched(task, time.getTime(), 0);    //    一次性工作,period 为 0
}

public void schedule(TimerTask task, long delay) {
    ...
    sched(task, System.currentTimeMillis()+delay, 0);    //    一次性工作,period 为 0
}

public void schedule(TimerTask task, long delay, long period) {
    ...
    sched(task, System.currentTimeMillis()+delay, -period);    //    固定延时模式,-period
}

public void schedule(TimerTask task, Date firstTime, long period) {
    ...
    sched(task, firstTime.getTime(), -period);    //    固定延时模式,-period
}

public void scheduleAtFixedRate(TimerTask task, long delay, long period) {
    ...
    sched(task, System.currentTimeMillis()+delay, period);    //    固定速率模式,period 为正
}

public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) {
    ...
    sched(task, firstTime.getTime(), period);    //    固定速率模式,period 为正
}

sched 办法外围代码:


private void sched(TimerTask task, long time, long period) {
   ...

    //    加锁,防止内部其余线程同时调用 cancel,同时拜访 queue 产生线程同步问题
    synchronized(queue) {

        //    如果线程已终止,抛出异样
        if (!thread.newTasksMayBeScheduled)
            throw new IllegalStateException("Timer already cancelled.");

        //    加锁,防止多线程拜访同一个工作产生线程同步问题
        synchronized(task.lock) {
            
            //    task 的状态必须为 VIRGIN,否则认为曾经退出调度或者曾经勾销了,防止反复的调度
            if (task.state != TimerTask.VIRGIN)
                throw new IllegalStateException("Task already scheduled or cancelled");
            
            //    设置下次执行工夫点
            task.nextExecutionTime = time;
            //    设置工夫距离
            task.period = period;
            //    工作状态变更为已调度
            task.state = TimerTask.SCHEDULED;
        }

        //    将工作增加到队列中
        queue.add(task);
        
        //    如果此工作是最近的工作,唤醒线程
        if (queue.getMin() == task)
            queue.notify();}
}

2. cancel 办法

cancel 办法个别是由内部其余线程调用,而 Timer 外部的线程也会对工作队列进行操作,因而加锁。

public void cancel() {synchronized(queue) {
        //    批改线程的循环执行标记,令线程可能终止
        thread.newTasksMayBeScheduled = false;
        //    清空工作队列
        queue.clear();
        //    唤醒线程
        queue.notify();}
}

3. purge 办法

当通过 TimerTask.cancel 将工作勾销后,Timer 的工作队列还援用着此工作,Timer 只有到了要执行时才会移除,其余时候并不会主动将此工作移除,须要调用 purge 办法进行清理。

public int purge() {
     int result = 0;

     synchronized(queue) {
         
         //    遍历队列,将 CANCELLED 状态的工作从工作队列中移除
         for (int i = queue.size(); i > 0; i--) {if (queue.get(i).state == TimerTask.CANCELLED) {queue.quickRemove(i);
                 result++;
             }
         }

         //    如果移除工作数不为 0,触发从新排序
         if (result != 0)
             queue.heapify();}

    //    返回移除工作数
     return result;
 }

三、TaskQueue

TaskQueue 是 Timer 类文件中封装的一个队列数据结构,外部默认是一个长度 128 的 TimerTask 数组,当工作退出时,检测到数组将满将会主动扩容 1 倍,并对数组元素依据下次执行工夫 nextExecutionTime 按工夫从近到远进行排序。

void add(TimerTask task) {
    // 检测数组长度,若不够则进行扩容
    if (size + 1 == queue.length)
        queue = Arrays.copyOf(queue, 2*queue.length);

    //    工作入队
    queue[++size] = task;

    //    排序
    fixUp(size);
}

fixUp 办法实现:

private void fixUp(int k) {while (k > 1) {
        int j = k >> 1;
        if (queue[j].nextExecutionTime <= queue[k].nextExecutionTime)
            break;
        TimerTask tmp = queue[j];  queue[j] = queue[k]; queue[k] = tmp;
        k = j;
    }
}

TaskQueue 中除了 fixUp 办法外还有一个 fixDown 办法,这两个其实就是堆排序算法,在算法专题中再进行具体介绍,只有记住他们的工作就是按工夫从近到远进行排序,最近的工作排在队首即可。

private void fixDown(int k) {
    int j;
    while ((j = k << 1) <= size && j > 0) {
        if (j < size &&
            queue[j].nextExecutionTime > queue[j+1].nextExecutionTime)
            j++; // j indexes smallest kid
        if (queue[k].nextExecutionTime <= queue[j].nextExecutionTime)
            break;
        TimerTask tmp = queue[j];  queue[j] = queue[k]; queue[k] = tmp;
        k = j;
    }
}

void heapify() {for (int i = size/2; i >= 1; i--)
        fixDown(i);
}

四、TimerThread

TimerThread 的外围代码位于 mainLoop 办法:

private void mainLoop() {
    
    //    死循环,从队列取工作执行
    while (true) {
        try {
            TimerTask task;
            boolean taskFired;
            
            //    对工作队列加锁
            synchronized(queue) {
                
                //    如果队列中没有工作,则进入期待,newTasksMayBeScheduled 是线程运行标记位,为 false 时将退出循环
                while (queue.isEmpty() && newTasksMayBeScheduled)
                    queue.wait();
                
                //    如果工作队列是空的还执行到这一步,阐明 newTasksMayBeScheduled 为 false,退出循环
                if (queue.isEmpty())
                    break; 

                long currentTime, executionTime;
                
                //    从队列获得最近的工作
                task = queue.getMin();
                
                //    加锁
                synchronized(task.lock) {
                    
                    //    如果工作状态是已勾销,则移除该工作,从新循环取工作
                    if (task.state == TimerTask.CANCELLED) {queue.removeMin();
                        continue; 
                    }
                    
                    //    以后工夫
                    currentTime = System.currentTimeMillis();
                    //    工作的执行工夫点
                    executionTime = task.nextExecutionTime;
                    
                    //    如果执行工夫点早于或等于以后工夫,即过期 / 工夫到了,则触发工作执行
                    if (taskFired = (executionTime<=currentTime)) {

                        //    如果工作 period=0,即一次性工作
                        if (task.period == 0) {

                            //    从队列移除一次性工作
                            queue.removeMin();


                            //    工作状态变更为已执行
                            task.state = TimerTask.EXECUTED;

                        } else {

                            //    可反复执行工作,从新进行调度,period<0 是固定时延,period>0 是固定速率
                            queue.rescheduleMin(
                              task.period<0 ? currentTime   - task.period    //    计算下次执行工夫
                                            : executionTime + task.period);
                        }
                    }
                }
                
                // taskFired 为 false 即工作尚未到执行工夫点,进行期待,等待时间是 执行工夫点 - 以后工夫点
                if (!taskFired)
                    queue.wait(executionTime - currentTime);
            }

            //    taskFired 为 true 示意已触发,执行工作
            if (taskFired)  
                task.run();} catch(InterruptedException e) {}}
}
退出移动版