執行緒池之ScheduledThreadPoolExecutor概述
簡介
在探討時 ThreadPoolExecutor 只介紹了FixedThreadPool、CachedThreadPool、SingleThreadExecutor,並沒有去介紹ScheduledThreadPoolExecutor,因為 ScheduledThreadPoolExecutor 與其他執行緒池的概念有些區別,它是一個支援任務週期性排程的執行緒池。
ScheduledThreadPoolExecutor 繼承 ThreadPoolExecutor,同時通過實現 ScheduledExecutorSerivce 來擴充套件基礎執行緒池的功能,使其擁有了排程能力。其整個排程的核心在於內部類 DelayedWorkQueue ,一個有序的延時佇列。
ScheduledThreadPoolExecutor 的出現,很好的彌補了傳統 Timer 的不足,具體對比看下錶:
Timer | ScheduledThreadPoolExecutor | |
---|---|---|
執行緒 | 單執行緒 | 多執行緒 |
多工 | 任務之間相互影響 | 任務之間不影響 |
排程時間 | 絕對時間 | 相對時間 |
異常 | 單任務異常, 後續任務受影響 |
無影響 |
構造方法
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
來源:簡書
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。