Java多线程总结(3)— Timer 和 TimerTask深入分析

来源:互联网 发布:华为软件商城下载 编辑:程序博客网 时间:2024/06/04 17:40

1. 基本概念

  java.util.Timer:是一个实用工具类,该类用来调度一个线程(schedule a thread),使它可以在将来某一时刻执行。 Java的Timer类可以调度一个任务运行一次,或定期循环运行。 Timer tasks should complete quickly. 即定时器中的操作要尽可能花费短的时间。

  java.util.TimerTask:是一个抽象类,它实现了Runnable接口。我们需要扩展该类以便创建自己的TimerTask,这个TimerTask可以被Timer调度。

  注意:默认, task执行线程不是daemon线程, 任务执行完,主线程(或其他启动定时器的线程)结束时,task线程并没有结束。如果调用者想要快速终止计时器的任务执行线程,调用者应该调用timer.cancel()方法。

public void timerTest() {    Timer myTimer = new Timer();    myTimer.schedule(new TimerTask() {        @Override        public void run() {            System.out.println("1s后运行");            myTimer.cancel(); // 需要手动cancel        }    }, 1000);}

2. 源码解析及案例

  Timer类是线程安全的,多进程不需要外部同步机制就可以共享同一个Timer对象。创建Timer对象,会创建一个java.util.TaskQueue实例,在执行定时任务时,将taskqueue对象作为锁,在指定时间间隔添加任务,在任何时刻只能有一个线程执行TimerTask。
  Timer类使用对象的wait和notify方法来调度任务。

  查看Timer源代码:

// TaskQueue队列,内部就是一个TimerTask[]数组private final TaskQueue queue = new TaskQueue();// Timer内部维持一个叫TimerThread的线程,传递TaskQueue队列private final TimerThread thread = new TimerThread(queue);// 创建Timer即启动线程public Timer(String name) {    thread.setName(name);    thread.start();// 启动线程,后面有分析TimerThread的run方法}public void schedule(TimerTask task, long delay) {    if (delay < 0)        throw new IllegalArgumentException("Negative delay.");    sched(task, System.currentTimeMillis()+delay, 0);}// 核心调度方法,time表示执行的绝对时间private void sched(TimerTask task, long time, long period) {    ...    // timer对象中的queue作为锁    synchronized(queue) {        if (!thread.newTasksMayBeScheduled)            throw new IllegalStateException("Timer already cancelled.");        synchronized(task.lock) {            if (task.state != TimerTask.VIRGIN)                throw new IllegalStateException(                    "Task already scheduled or cancelled");            // 设置下次执行的时间            task.nextExecutionTime = time;            task.period = period;            // 设置task为调度状态            task.state = TimerTask.SCHEDULED;        }        // 将当前待执行的task添加到队列中        queue.add(task);        // 队列中取出的head task为当前task        if (queue.getMin() == task)            // 在任何时刻只能有一个线程执行TimerTask            queue.notify();    }}

  其中Timer中启动的TimerThread的run方法:

private TaskQueue queue;TimerThread(TaskQueue queue) {    this.queue = queue;}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) {                // 如果queue队列为空,则将线程阻塞,等待task                while (queue.isEmpty() && newTasksMayBeScheduled)                    queue.wait();                if (queue.isEmpty())                    break; // Queue is empty and will forever remain; die                // Queue nonempty; look at first evt and do the right thing                long currentTime, executionTime;                // 队列中获取task                task = queue.getMin();                synchronized(task.lock) {                    // 此处说明可以通过cancel()设置终止task的执行,但TimerThread并没有终止                    if (task.state == TimerTask.CANCELLED) {                        queue.removeMin(); // 从队列中移除                        continue;  // No action required, poll queue again                    }                    currentTime = System.currentTimeMillis();                    executionTime = task.nextExecutionTime;                    if (taskFired = (executionTime<=currentTime)) {                        if (task.period == 0) { // Non-repeating, remove                            queue.removeMin();                            task.state = TimerTask.EXECUTED;                        } else { // Repeating task, reschedule                            queue.rescheduleMin(                              task.period<0 ? currentTime   - task.period                                            : executionTime + task.period);                        }                    }                }                // 还未到执行时间,则等待相应的时间                if (!taskFired) // Task hasn't yet fired; wait                    queue.wait(executionTime - currentTime);            }            if (taskFired)  // Task fired; run it, holding no locks                task.run(); // 执行task的run方法!        } catch(InterruptedException e) {        }    }}

  TimerTask的调度流程:由以上分析可知,当我们创建一个Timer时,同时内部创建了一个TimerThread线程,并启动它,该线程会不断扫描从Timer传递进来的task队列,如果为空,则wait()阻塞该线程;当timer调用shedule方法的时候,将传递的task添加到队列中,同时调用queue.notify()方法唤醒TimerThread线程,则从队列中取出task根据给定的等待时间wait等待,等待完成后执行task.run();启动任务。(这种结构可以应用到简单的爬虫中)
  这里写图片描述

  如何终止Timer线程
  默认情况下,创建的timer线程会一直执行,一般通过下面的方式来终止timer线程:

  1. 调用timer的cancle方法
  2. 把timer线程设置成daemon线程,(new Timer(true)创建daemon线程),在jvm里,如果所有用户线程结束,那么守护线程也会被终止,不过这种方法一般不用。

  下面给出一个Timer执行多个task的简单案例:

public void multileTasksShareOneTimer() {    Timer myTimer = new Timer();    TimerTask task1 = new TimerTask() {        @Override        public void run() {            System.out.println(Thread.currentThread().getName() +" 1s后执行task1");            //myTimer.cancel();        }    };    TimerTask task2 = new TimerTask() {        @Override        public void run() {            System.out.println(Thread.currentThread().getName() +" 2s后执行task2");            //myTimer.cancel();        }    };    myTimer.schedule(task1, 1000);    // task2.cancel();    myTimer.schedule(task2, 2000);}

  运行输出:注意此时Timer的TimerThread并没有结束,因为在mainLoop()等待task而wait进入阻塞状态!
这里写图片描述
  如果设置了task2.cancel();则调度执行task2会抛出异常:
这里写图片描述

  定时器实际使用的场景还是很多的,比如下面的,在每天零晨备份数据库,等等。

/** * 每天0晨备份数据库 */@SuppressWarnings("deprecation")public void backupDatabase() {    Timer timer = new Timer();    TimerTask task = new TimerTask() {        @Override        public void run() {            // 备份数据库            System.out.println("数据库备份...");            // other operation        }    };    Date firstTime = new Date();    firstTime.setHours(0);    firstTime.setMinutes(0);    firstTime.setSeconds(0);    long oneDayPeriod = 24 * 60 * 60 * 1000;    timer.scheduleAtFixedRate(task, firstTime, oneDayPeriod);    // timer.schedule(task, firstTime, oneDayPeriod);}

  最后:如果是简单的定时调度,使用Timer就够了,如果复杂的调度任务,可以考虑使用Quartz

  转载请注明出处:Java多线程总结(3)— Timer 和 TimerTask深入分析

1 0