1. 程式人生 > 實用技巧 >執行緒池之ScheduledThreadPoolExecutor概述

執行緒池之ScheduledThreadPoolExecutor概述

簡介

在探討時 ThreadPoolExecutor 只介紹了FixedThreadPool、CachedThreadPool、SingleThreadExecutor,並沒有去介紹ScheduledThreadPoolExecutor,因為 ScheduledThreadPoolExecutor 與其他執行緒池的概念有些區別,它是一個支援任務週期性排程的執行緒池。

ScheduledThreadPoolExecutor 繼承 ThreadPoolExecutor,同時通過實現 ScheduledExecutorSerivce 來擴充套件基礎執行緒池的功能,使其擁有了排程能力。其整個排程的核心在於內部類 DelayedWorkQueue ,一個有序的延時佇列。

ScheduledThreadPoolExecutor類圖.png

ScheduledThreadPoolExecutor 的出現,很好的彌補了傳統 Timer 的不足,具體對比看下錶:

TimerScheduledThreadPoolExecutor
執行緒 單執行緒 多執行緒
多工 任務之間相互影響 任務之間不影響
排程時間 絕對時間 相對時間
異常 單任務異常,
後續任務受影響
無影響

構造方法

ScheduledThreadPoolExecutor有三個構造形式:

public ScheduledThreadPoolExecutor(int corePoolSize,
                                    ThreadFactory threadFactory) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue(), threadFactory);
}

public ScheduledThreadPoolExecutor(int corePoolSize,
                                   RejectedExecutionHandler handler) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue(), handler);
}

public ScheduledThreadPoolExecutor(int corePoolSize,
                                   ThreadFactory threadFactory,
                                   RejectedExecutionHandler handler) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue(), threadFactory, handler);
}

關於父類的構造可參見 ThreadPoolExecutor。當然我們也可以使用工具類Executors的newScheduledThreadPool的方法,快速建立。注意這裡使用的DelayedWorkQueue

ScheduledThreadPoolExecutor沒有提供帶有最大執行緒數的建構函式的,預設是Integer.MAX_VALUE,說明其可以無限制的開啟任意執行緒執行任務,在大量任務系統,應注意這一點,避免記憶體溢位。

核心方法

核心方法主要介紹ScheduledThreadPoolExecutor的排程方法,其他方法與 ThreadPoolExecutor 一致。排程方法均由 ScheduledExecutorService 介面定義:

public interface ScheduledExecutorService extends ExecutorService {
    // 特定時間延時後執行一次Runnable
    public ScheduledFuture<?> schedule(Runnable command,
                                       long delay, TimeUnit unit);
    // 特定時間延時後執行一次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);
}

程式碼中註釋了每個方法的作用,需注意固定週期與固定延時的區別。下面分別對這些方法進行測試:

public class ScheduledPoolTest {
    
    private static final SimpleDateFormat FORMAT = new SimpleDateFormat("hh:mm:ss");
    
    private static final Random RANDOM = new Random();
    
    /**
     * 輸出:
     *  11:04:32
        11:04:35
     */
    public static void schedule() {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
        printTime();
        scheduledExecutorService.schedule(new Task(), 3, TimeUnit.SECONDS);
    }
    
    /**
     * 輸出:
     *  11:05:34
        11:05:36
        11:05:46
        11:05:56
        11:06:06
        11:06:16
        ......
     */
    public static void scheduleAtFixedRate() {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
        printTime();
        scheduledExecutorService.scheduleAtFixedRate(new Task(), 2, 10, TimeUnit.SECONDS);
    }
    
    /**
     * 輸出:
     *  11:07:39
        11:07:41
        11:07:54
        11:08:08
        11:08:22
        11:08:33
        ......
     */
    public static void scheduleWithFixedDelay() {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
        printTime();
        scheduledExecutorService.scheduleWithFixedDelay(new Task(), 2, 10, TimeUnit.SECONDS);
    }
    
    static class Task implements Runnable{
        public void run() {
            printTime();
            try {
                Thread.sleep(RANDOM.nextInt(5) * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    public static void printTime() {
        Date date = new Date();
        System.out.println(FORMAT.format(date));
    }
}

為了體現scheduleAtFixedRate和scheduleWithFixedDelay的差別,在程式碼中我們加入了隨機睡眠時間,使任務執行不確定。從註釋中的輸出我們可以看到scheduleAtFixedRate的任務執行週期不受任務執行時間的影響,而scheduleWithFixedDelay的任務執行週期受任務執行時間影響較大。

但需注意,如果任務的執行時間超過任務排程週期,比如任務執行需要10s,而給定執行時間間隔是5s的話,任務的排程是在任務10s執行完之後立即重新執行,而不是5s的週期。

總結

ScheduledThreadPoolExecutor 在 ThreadPoolExecutor 的基礎上擴充套件了 執行緒週期排程功能,使用時應注意控制其排程的時間點。

多執行緒系列目錄(不斷更新中):



作者:徐志毅
連結:https://www.jianshu.com/p/5d994ee6d4ff
來源:簡書
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。