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

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();启动任务。(这种结构可以应用到简单的爬虫中)
  Java多线程总结(3)— Timer 和 TimerTask深入分析_第1张图片

  如何终止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会抛出异常:
Java多线程总结(3)— Timer 和 TimerTask深入分析_第2张图片

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

/**
 * 每天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深入分析

你可能感兴趣的:(Java多线程总结(3)— Timer 和 TimerTask深入分析)