Timer和TimerTask
基本用法
TimerTask表示一個定時任務,它是一個抽象類,實現了Runnable,具體的定時任務需要繼承該類,實現run方法。
Timer是一個具體類,它負責定時任務的調度和執行,主要方法有:
// 在指定絕對時間time運行任務task
public void schedule(TimerTask task, Date time);
// 在當前時間延時delay毫秒後運行任務task
public void schedule(TimerTask task, long delay);
// 固定延時重複執行,第一次計劃執行時間爲firstTime,如果firstTime小於當前時間,則立即執行
// 後一次的計劃執行時間爲前一次“實際執行”時間加上period
public void schedule(TimerTask task, Date firstTime, long period);
// 固定延時重複執行,第一次計劃執行時間爲當前時間加上delay毫秒
public void schedule(TimerTask task, long delay, long period);
// 固定頻率重複執行。第一次計劃執行時間爲firstTime,如果firstTime小於當前時間,則立即執行
// 後一次的計劃執行時間爲前一次“計劃執行”時間加上period
public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period);
// 固定頻率重複執行。第一次計劃執行時間爲當前時間加上delay毫秒
public void scheduleAtFixedRate(TimerTask task, long delay, long period);
固定延時和固定頻率的區別:二者都是重複執行,但後一次任務執行相對的時間是不一樣的。 固定延時它是基於上一次任務的“實際”執行時間來算的,如果由於某種原因上一次執行時間延遲了,那麼這次任務也延時。
基本示例
public class TimerFixedDelay {
static class LongRunningTask extends TimerTask {
@Override
public void run() {
try {
System.out.println("start sleep");
Thread.sleep(5000);
System.out.println("sleep over");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class FixedDelayTask extends TimerTask {
@Override
public void run() {
System.out.println("FixedDelayTask:" + System.currentTimeMillis());
}
}
static class FixedRateTask extends TimerTask {
@Override
public void run() {
System.out.println("FixedRateTask:" + System.currentTimeMillis());
}
}
public static void main(String[] args) {
Timer timer = new Timer();
// 延時10毫秒後執行,只執行一次,但耗時5秒
timer.schedule(new LongRunningTask(), 10);
// 延時100毫秒後執行,相對於上次實際執行時間,每隔2秒執行一次
timer.schedule(new FixedDelayTask(), 100, 2000);
// 延時100毫秒後執行,相對於上次計劃執行時間,每隔1秒執行一次,
timer.scheduleAtFixedRate(new FixedRateTask(), 100, 1000);
}
}
通過這個示例發現第二和第三個任務只有在第一個任務運行結束後纔會開始運行。
同時可以看出固定延時和固定頻率的區別,第二個任務是固定延時,它是在第一個任務運行結束後纔開始運行,2秒一次。第三個任務是固定頻率,在第一個任務運行結束後才運行,但它會把之前沒有運行的次數補過來,一下運行了5次。
基本原理
Timer內部主要由任務隊列和Timer線程兩部分組成。
任務隊列是一個基於堆實現的優先級隊列,按照下次執行的時間排優先級
Timer線程負責執行所有的定時任務,一個Timer對象只有一個Timer線程,所以任務會被延遲。
對於固定延時的任務,延時相對的是任務執行前的當前時間,而不是任務執行後。
對於固定頻率的任務,延時相對的是最先的計劃
死循環
一個Timer對象只有一個Timer線程,這意味着:定時任務不能耗時太長,更不能無限循環。
異常處理
在執行任何一個任務的run方法時,一旦run拋出異常,Timer線程就會退出,從而所有定時任務都會被取消
小結
- 後臺只有一個線程在運行
- 固定頻率的任務被延遲後,可能會立即執行多次,將次數不夠
- 固定延時任務的延時相對的是任務執行前的時間
- 不要在定時任務中使用無限循環,會導致後面的任務無法執行
- 一個定時任務的未處理異常會導致所有定時任務被取消
- Timer 對提交的任務調度是基於絕對時間而不是相對時間的,所以通過其提交的任務對系統時鐘的改變是敏感的(譬如提交延遲任務後修改了系統時間會影響其執行)
ScheduledExecutorService
基本用法
ScheduledExecutorService 是一個接口
public interface ScheduledExecutorService extends ExecutorService {
// 單次執行,在指定延時delay後運行command
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);
// 單次執行,在指定延時delay後運行callable
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);
}
返回類型ScheduledFuture,它是一個接口,擴展了Future和Delayed。
對於固定延時的任務,它是從任務執行後開始算的,第一次執行爲當前時間 + initialDelay,第二次爲第一次任務執行結束後再加上delay
對於固定頻率的任務,第一次執行時間爲當前時間 + initialDelay,第二次爲當前時間 + initialDelay + period
ScheduledExecutorService的主要實現類是ScheduledTheradPoolExecutor,它是線程池ThreadPoolExecutor的子類。它的任務隊列是一個無界的優先級隊列,所以最大線程數對它沒有作用。
工廠類Executors提供了一些創建ScheduledTheradPoolExecutor的方法
// 單線程的定時任務執行服務
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1, threadFactory));
}
// 多線程的定時任務執行服務
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public static ScheduledExecutorService newScheduledThreadPool(
int corePoolSize, ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
小結
- 背後是線程池,可以有多個線程執行任務
- 在任務執行後再設置下次執行時間,對於固定延時的任務更合理
- 任務執行的線程會普通任務執行過程中的所有異常,一個定時任務的異常不會影響其他定時任務,不過,發生異常的任務(即使是一個重複任務)不會再被調度