Timer/TimerTask/ScheduledExecutorService周期性调度

周期性任务调度前世

在JDK 5.0之前,java.util.Timer/TimerTask是唯一的内置任务调度方法,而且在很长一段时间里很热衷于使用这

种方式进行周期性任务调度。

首先研究下Timer/TimerTask的特性(至于javax.swing.Timer就不再研究了)。

public void schedule(TimerTask task, long delay, long period) {
    if (delay < 0)
        throw new IllegalArgumentException("Negative delay.");
    if (period <= 0)
        throw new IllegalArgumentException("Non-positive period.");
    sched(task, System.currentTimeMillis()+delay, -period);
}
public void scheduleAtFixedRate(TimerTask task, long delay, long period) {
    if (delay < 0)
        throw new IllegalArgumentException("Negative delay.");
    if (period <= 0)
        throw new IllegalArgumentException("Non-positive period.");
    sched(task, System.currentTimeMillis()+delay, period);
}
 

public class Timer {

    private TaskQueue queue = new TaskQueue();
    /**
     * The timer thread.
     */
    private TimerThread thread = new TimerThread(queue);
 
 

java.util.TimerThread.mainLoop()

private void mainLoop() {
    while (true) {
        try {
            TimerTask task;
            boolean taskFired;
            synchronized(queue) {
                // Wait for queue to become non-empty
                while (queue.isEmpty() && newTasksMayBeScheduled)
                    queue.wait();
                if (queue.isEmpty())
                    break; // Queue is empty and will forever remain; die
。。。。。。
                 
                if (!taskFired) // Task hasnt yet fired; wait
                    queue.wait(executionTime - currentTime);
            }
            if (taskFired)  // Task fired; run it, holding no locks
                task.run();
        } catch(InterruptedException e) {
        }
    }
}
 

上面三段代码反映了Timer/TimerTask的以下特性:

Timer对任务的调度是基于绝对时间的。 
所有的TimerTask只有一个线程TimerThread来执行,因此同一时刻只有一个TimerTask在执行。 
任何一个TimerTask的执行异常都会导致Timer终止所有任务。 
由于基于绝对时间并且是单线程执行,因此在多个任务调度时,长时间执行的任务被执行后有可能导致短

时间任务快速在短时间内被执行多次或者干脆丢弃多个任务。 
由于Timer/TimerTask有这些特点(缺陷),因此这就导致了需要一个更加完善的任务调度框架来解决这些

问题。

周期性任务调度今生

java.util.concurrent.ScheduledExecutorService的出现正好弥补了Timer/TimerTask的缺陷。

public interface ScheduledExecutorService extends ExecutorService {
    public ScheduledFuture<?> schedule(Runnable command,
                       long delay, TimeUnit unit);
 
    public <V> ScheduledFuture<V> schedule(Callable<V> callable,
                       long delay, TimeUnit unit);
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                          long initialDelay,
                          long period,
                          TimeUnit unit);
 
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                             long initialDelay,
                             long delay,
                             TimeUnit unit);
}
 

首先ScheduledExecutorService基于ExecutorService,是一个完整的线程池调度。另外在提供线程池的基础上增加

了四个调度任务的API。

schedule(Runnable command,long delay, TimeUnit unit):在指定的延迟时间一次性启动任务(Runnable),没有

返回值。 
schedule(Callable<V> callable, long delay, TimeUnit unit):在指定的延迟时间一次性启动任务(Callable),携带

一个结果。 
scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit):建并执行一个在给定初始

延迟后首次启用的定期操作,后续操作具有给定的周期;也就是将在 initialDelay 后开始执行,然后在 initialDelay+period 后执行,接着在 initialDelay + 2 * period 后执行,依此类推。如果任务的任何一个执行

遇到异常,则后续执行都会被取消。否则,只能通过执行程序的取消或终止方法来终止该任务。如果此任务的

任何一个执行要花费比其周期更长的时间,则将推迟后续执行,但不会同时执行。 
scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit):创建并执行一个在给定

初始延迟后首次启用的定期操作,随后,在每一次执行终止和下一次执行开始之间都存在给定的延迟。如果

任务的任一执行遇到异常,就会取消后续执行。否则,只能通过执行程序的取消或终止方法来终止该任务。 
上述API解决了以下几个问题:

ScheduledExecutorService任务调度是基于相对时间,不管是一次性任务还是周期性任务都是相对于任务加入

线程池(任务队列)的时间偏移。 
基于线程池的ScheduledExecutorService允许多个线程同时执行任务,这在添加多种不同调度类型的任务是非常

有用的。 
同样基于线程池的ScheduledExecutorService在其中一个任务发生异常时会退出执行线程,但同时会有新的线程

补充进来进行执行。 
ScheduledExecutorService可以做到不丢失任务。 
下面的例子演示了一个任务周期性调度的例子。

package xylz.study.concurrency.executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduledExecutorServiceDemo {
    public static void main(String[] args) throws Exception{
        ScheduledExecutorService execService =   Executors.newScheduledThreadPool(3);
        execService.scheduleAtFixedRate(new Runnable() {
            public void run() {
                System.out.println(Thread.currentThread().getName()+" -> "+System.currentTimeMillis());
                try {
                    Thread.sleep(2000L);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, 1, 1, TimeUnit.SECONDS);
        //
        execService.scheduleWithFixedDelay(new Runnable() {
            public void run() {
                System.out.println(Thread.currentThread().getName()+" -> "+System.currentTimeMillis());
                try {
                    Thread.sleep(2000L);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, 1, 1, TimeUnit.SECONDS);
        Thread.sleep(5000L);
        execService.shutdown();
    }
}
 

一次可能的输出如下:

pool-1-thread-1 -> 1294672392657
pool-1-thread-2 -> 1294672392659
pool-1-thread-1 -> 1294672394657
pool-1-thread-2 -> 1294672395659
pool-1-thread-1 -> 1294672396657
 

在这个例子中启动了默认三个线程的线程池,调度两个周期性任务。第一个任务是每隔1秒执行一次,第二

个任务是以固定1秒的间隔执行,这两个任务每次执行的时间都是2秒。上面的输出有以下几点结论:

任务是在多线程中执行的,同一个任务应该是在同一个线程中执行。 
scheduleAtFixedRate是每次相隔相同的时间执行任务,如果任务的执行时间比周期还长,那么下一个任务将

立即执行。例如这里每次执行时间2秒,而周期时间只有1秒,那么每次任务开始执行的间隔时间就是2秒。 
scheduleWithFixedDelay描述是下一个任务的开始时间与上一个任务的结束时间间隔相同。流入这里每次执行

时间2秒,而周期时间是1秒,那么两个任务开始执行的间隔时间就是2+1=3秒。 
事实上ScheduledExecutorService的实现类ScheduledThreadPoolExecutor是继承线程池类ThreadPoolExecutor的,

因此它拥有线程池的全部特性。但是同时又是一种特殊的线程池,这个线程池的线程数大小不限,任务队列

是基于DelayQueue的无限任务队列。具体的结构和算法在以后的章节中分析。

由于ScheduledExecutorService拥有Timer/TimerTask的全部特性,并且使用更简单,支持并发,而且更安全,

因此没有理由继续使用Timer/TimerTask,完全可以全部替换。需要说明的一点事构造ScheduledExecutorService

线程池的核心线程池大小要根据任务数来定,否则可能导致资源的浪费。

posted @ 2013-03-28 17:02  绝尘的神马  阅读(569)  评论(0编辑  收藏  举报