周期性任務調度前世
在JDK 5.0之前,java.util.Timer/TimerTask是唯一的內置任務調度方法,而且在很長一段時間里很熱衷于使用這種方式進行周期性任務調度。
首先研究下Timer/TimerTask的特性(至于javax.swing.Timer就不再研究了)。
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);
}
private TaskQueue queue = new TaskQueue();
/**
* The timer thread.
*/
private TimerThread thread = new TimerThread(queue);
java.util.TimerThread.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 hasn't 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 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可以做到不丟失任務。
下面的例子演示了一個任務周期性調度的例子。
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-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線程池的核心線程池大小要根據任務數來定,否則可能導致資源的浪費。
[本文地址:http://www.tkk7.com/Files/xylz/Inside.Java.Concurrency_32.ThreadPool.part5_ScheduledExecutorService.pdf]