ScheduledThreadPoolExecutor 使用線程池執行定時任務

在現實世界裏,我們總是免不了要定期去做一件事情(比如上課)—— 在計算機的世界裏,更是如此。比如我們手機每天叫我們起牀的電子鬧鐘,某些網站會定期向我們發送一些推薦相關的郵件,集羣中我們需要每隔一定時間檢查是否有機器宕機等。

在 使用線程池 中已經介紹,JDK 1.5 時,標準類庫添加了對線程池的支持,然後在線程池核心實現 ThreadPoolExecutor 的基礎上,實現了 ScheduledThreadPoolExecutor,作爲可以 定時和週期性執行任務 的線程池。ScheduledThreadPoolExecutor 的類圖如下:
ScheduledThreadPoolExecutor 的類圖

ScheduledThreadPoolExecutor 實現了 ScheduledExecutorService 接口,ScheduledExecutorService 繼承了 ExecutorService 接口,所以首先 ScheduledThreadPoolExecutor 是一個 ExecutorService (線程池),然後除了具有線程池的功能,它還有定時和週期性執行任務的功能。ScheduledExecutorService 除了從 ExecutorService 繼承的方法外,還包括如下四個方法:
ScheduledExecutorService 定義的四個方法

第一個 Schedule 方法:
Schedule 方法

delay 指定的時間後,執行指定的 Runnable 任務,可以通過返回的 ScheduledFuture<?> 與該任務進行交互。

第二個 Schedule 方法:
第二個 Schedule 方法

delay 指定的時間後,執行指定的 Callable 任務,可以通過返回的 ScheduledFuture 與該任務進行交互。

(ScheduledFuture 接口 繼承自 Future 接口,所以 ScheduledFuture 和任務的交互方式與 Future 一致。所以通過ScheduledFuture,可以 判斷定時任務是否已經完成,獲得定時任務的返回值,或者取消任務等)

scheduleAtFixedRate 方法:
scheduleAtFixedRate 方法

initialDelay 指定的時間後,開始按週期 period 執行指定的 Runnable 任務。
假設調用該方法後的時間點爲 0,那麼第一次執行任務的時間點爲 initialDelay,第二次爲 initialDelay + period,第三次爲 initialDelay + period + period,以此類推。

scheduleWithFixedDelay 方法:
scheduleWithFixedDelay 方法

initialDelay 指定的時間後,開始按指定的 delay 延期性的執行指定的 Runnable 任務。
假設調用該方法後的時間點爲 0,每次任務需要耗時 T(i)(i 爲第幾次執行任務),那麼第一次執行任務的時間點爲 initialDelay,第一次完成任務的時間點爲 initialDelay + T(1),則第二次執行任務的時間點爲 initialDelay + T(1) + delay;第二次完成任務的時間點爲 initialDelay + (T(1) + delay) + T(2),所以第三次執行任務的時間點爲 initialDelay + T(1) + delay + T(2) + delay,以此類推。

我們來實踐下 ScheduledThreadPoolExecutor 的 scheduleAtFixedRate 方法:

複製代碼
1 public class ScheduledExecutorServiceTest {
2
3 public static void main(String[] args) throws Exception {
4 ScheduledExecutorService timer = Executors.newSingleThreadScheduledExecutor();
5
6 TimerTask timerTask = new TimerTask(2000); // 任務需要 2000 ms 才能執行完畢
7
8 System.out.printf("起始時間:%s\n\n", new SimpleDateFormat("HH:mm:ss").format(new Date()));
9
10 // 延時 1 秒後,按 3 秒的週期執行任務
11 timer.scheduleAtFixedRate(timerTask, 1000, 3000, TimeUnit.MILLISECONDS);
12 }
13
14 private static class TimerTask implements Runnable {
15
16 private final int sleepTime;
17 private final SimpleDateFormat dateFormat;
18
19 public TimerTask(int sleepTime) {
20 this.sleepTime = sleepTime;
21 dateFormat = new SimpleDateFormat("HH:mm:ss");
22 }
23
24 @Override
25 public void run() {
26 System.out.println("任務開始,當前時間:" + dateFormat.format(new Date()));
27
28 try {
29 System.out.println("模擬任務運行...");
30 Thread.sleep(sleepTime);
31 } catch (InterruptedException ex) {
32 ex.printStackTrace(System.err);
33 }
34
35 System.out.println("任務結束,當前時間:" + dateFormat.format(new Date()));
36 System.out.println();
37 }
38
39 }
40 }
複製代碼
運行結果:運行結果









































可以看到運行結果完全符合預期 —— 延時 1 秒後,每隔 3 秒執行一次任務。

上面是任務的運行時間小於週期時間的情況 —— 那如果任務運行的時間大於給定的執行週期呢?(比如任務運行需要 3 s,但是我們指定的週期爲 2 s)

修改 main 方法:

複製代碼
1 public static void main(String[] args) throws Exception {
2 ScheduledExecutorService timer = Executors.newScheduledThreadPool(2);
3
4 TimerTask timerTask = new TimerTask(3000); // 每個任務需要 3000 ms 才能執行完畢
5
6 System.out.printf("起始時間:%s\n\n", new SimpleDateFormat("HH:mm:ss").format(new Date()));
7
8 timer.scheduleAtFixedRate(timerTask, 1000, 2000, TimeUnit.MILLISECONDS);
9 }
複製代碼
運行結果:










可以看到此時雖然我們指定的週期爲 2 s,但是因爲任務的運行就需要 3 s(超過週期),所以這種情況下 scheduleAtFixedRate 的處理方式爲 上一次任務剛完成,則緊接着立即運行下一次任務,而不是使用線程池中的空閒線程來運行任務以維護 2 秒這個週期 —— 由此可見,每個定時任務在 ScheduledThreadPoolExecutor 中,都是串行運行的,即下一次運行任務一定在上一次任務結束之後。

(scheduleWithFixedDelay 方法 的使用也十分簡單,請有興趣的讀者自己實踐)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章