021_java.util.Timer

使用例子

Timer是jdk自带的定时器,我们先来看看这个类是怎么用的。如下:

public class TimerTest {
    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("执行:"+System.currentTimeMillis());
            }
        }, 1000, 1000);
        System.out.println("开始执行:"+System.currentTimeMillis());
    }
}

定义一个Timer的定时器,使用schedule方法传递TimerTask作为任务,第一个1000表示在执行schedule方法之后1000ms之后再开始执行,第二个1000指的是每1000ms执行一次。获得的输出是:

开始执行:1722405005077
执行:1722405006091
执行:1722405007100
执行:1722405008103

可以看到,差不多会隔1000ms执行一次。

构成模型

我们先大致上看看Timer的工作原理,首先Timer内部会存在TimerThread会一直执行,执行的目标就是我们传入Timer的TimeTask,而TimeTask使用TimeQueue来存储,TimeQueue使用堆的方式按照时间进行排序。那么意味着TimeThread获得TimeTask是从队列中捕获的。我们使用schedule方法可以将TimeTask放入TimeQueue中。如图所示:
image.png

TimeTask内部类

在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。
TimerTask中,有如下成员变量:

//	用于加锁控制多线程修改TimerTask内部状态
final Object lock = new Object();

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

//	任务的下一次执行时间点
long nextExecutionTime;

//  任务执行的时间间隔。正数表示固定速率;负数表示固定时延;0表示只执行一次
long period = 0;

TimerTask中有三个比较重要的方法:

  • run:实现了Runnable接口,创建TimerTask需要重写此方法,编写任务执行代码
  • cancel:取消任务
  • scheduledExecutionTime:计算执行时间点

源码如下:

public boolean cancel() {
    // 使用synchronized加锁,避免线程同步问题,cancel在修改状态前进行了加锁操作
    // Timer内部的线程会对TimerTask状态进行修改,而调用cancel方法一般会是另外一个线程
    synchronized(lock) {
        boolean result = (state == SCHEDULED);
        state = CANCELLED;
        return result;
    }
}

// 该方法返回此任务的下次执行时间点
public long scheduledExecutionTime() {
    synchronized(lock) {
        return (period < 0 ? nextExecutionTime + period
                           : nextExecutionTime - period);
    }
}

TaskQueue类

TaskQueue是Timer类文件中封装的一个队列数据结构,内部默认是一个长度128的TimerTask数组。

private TimerTask[] queue = new TimerTask[128];

private int size = 0;

当任务加入时,检测到数组将满将会自动扩容1倍,并对数组元素根据下次执行时间nextExecutionTime按时间从近到远进行排序。

void add(TimerTask task) {
    // 检测数组长度,若不够则进行扩容
    if (size + 1 == queue.length)
        queue = Arrays.copyOf(queue, 2*queue.length);
    
    //	任务入队
    // 注意这里是 ++Size,初始化意味着在1号位占用,0号位一直是null
    queue[++size] = task;
    
    //	排序
    fixUp(size);
}

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;
    }
}

fixUp是自底向上进行堆化,逻辑和我们之前聊的PriorityQueue的逻辑一致。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方法:

public void run() {
    try {
        mainLoop();
    } finally {
        // Someone killed this Thread, behave as if Timer cancelled
        synchronized(queue) {
            newTasksMayBeScheduled = false;
            queue.clear();  // Eliminate obsolete references
        }
    }
}

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) {
        
        }
    }
}

Timer类

经过上面的子项分析,Timer类就清晰了。在Timer中存在两个重要变量,分别关联了上面提到了重要对象

private final TaskQueue queue = new TaskQueue();

private final TimerThread thread = new TimerThread(queue);

schedule方法

无论是使用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();
    }
}

cancel方法

除了加任务的sche方法,也有针对任务的取消方法:

public void cancel() {
    synchronized(queue) {
        //	修改线程的循环执行标志,令线程能够终止
        thread.newTasksMayBeScheduled = false;
        //	清空任务队列
        queue.clear();
        //	唤醒线程
        queue.notify();
    }
}

当通过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;
}

总结

Timer的功能由好几个类共同协作完成,一个是封装执行主体的TimeTask,使用TaskQueue利用堆结构进行存储。Timer内存在TimerThread执行具体任务,从TaskQueue中获得任务。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值