在現代 Java 開發中,任務排程是一項不可或缺的功能。無論是定時資料同步、定期清理無效快取,還是實現任務重試,ScheduledThreadPoolExecutor
都是一個強大的工具。本文將從其基本概念、核心方法、應用場景以及最佳化建議等方面,深入探討這個 Java 併發工具。
基本概念
ScheduledThreadPoolExecutor 是 Java 中 java.util.concurrent 包提供的一個強大的執行緒池實現,專用於排程和執行定時任務或週期性任務。它繼承自 ThreadPoolExecutor,並擴充套件了其功能,允許更精確地安排任務的執行時間。
- 繼承關係:它是
ThreadPoolExecutor
的子類,同時實現了ScheduledExecutorService
介面。 - 核心特點:除了普通執行緒池的併發任務管理功能外,它可以精準地排程任務,包括一次性延遲執行和週期性重複執行。
特點總結:
- 執行緒複用:透過執行緒池複用機制,避免了執行緒頻繁建立和銷燬的開銷。
- 多工併發支援:支援同時排程多個任務。
- 定時精度:基於系統時間,排程精確且高效。
- 任務中斷機制:能夠在需要時靈活關閉或取消任務。
建立和配置
構造方法
ScheduledThreadPoolExecutor
提供了多種構造方法,最常見的是以下兩種形式:
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue(), threadFactory);
}
- 引數說明:
-
corePoolSize
:核心執行緒數,決定了同時執行的執行緒數量。 - 如果需要更多定製化引數,可以透過
ThreadFactory
自定義執行緒的優先順序、命名等。
-
配置建議
- 核心執行緒數 (
corePoolSize
):根據任務複雜度和執行時間確定,通常等於或略大於 CPU 核心數。 - 拒絕策略:預設策略會丟擲異常,建議結合業務需要設定,比如丟棄舊任務或呼叫方處理策略。
- 執行緒工廠:透過自定義
ThreadFactory
,可以命名執行緒池中的執行緒,方便排查問題。
核心方法
以下是 ScheduledThreadPoolExecutor
的幾個核心方法,它們構成了定時任務排程的主要功能。
一次性延遲任務
schedule(Runnable command, long delay, TimeUnit unit)
此方法延遲指定時間後執行任務。例如:
executor.schedule(() -> System.out.println("延遲任務"), 5, TimeUnit.SECONDS);
固定速率任務
scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
固定時間間隔重複執行任務,不受任務執行時間影響。例如:
executor.scheduleAtFixedRate(() -> {
System.out.println("固定速率執行:" + System.currentTimeMillis());
}, 1, 3, TimeUnit.SECONDS);
固定延遲任務
scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)
在每次任務完成後,延遲指定時間再開始下一次執行。例如:
executor.scheduleWithFixedDelay(() -> {
System.out.println("固定延遲執行:" + System.currentTimeMillis());
}, 1, 2, TimeUnit.SECONDS);
關閉執行緒池
-
shutdown()
:優雅關閉執行緒池,等待任務完成後退出。 -
shutdownNow()
:立即中斷所有任務。 -
isShutdown()
和isTerminated()
:用於檢查執行緒池的狀態。
注意事項
避免任務阻塞
當任務執行時間過長時,ScheduledThreadPoolExecutor 可能會因為執行緒資源不足而導致任務無法及時排程執行,最終出現執行緒池飽和的情況。以下是應對這種問題的幾種策略:
- 將耗時的任務分解為多個小任務,併合理安排其執行順序。
- 為執行緒池設定更大的核心執行緒數,確保能夠容納更多的併發任務。
- 將耗時任務交給其他執行緒池或非同步框架(如 CompletableFuture),釋放主執行緒池資源。
- 對任務排程進行限流,確保任務不會超出執行緒池的承載能力。
- 設定任務的最大執行時間,超時後強制中斷,防止任務佔用執行緒過久。
異常處理
當週期性任務丟擲未捕獲的異常時,ScheduledThreadPoolExecutor 中負責執行該任務的執行緒可能會終止,導致任務無法繼續排程執行。因此,需要為任務新增異常處理邏輯,以確保執行緒的穩定執行。
下面是一個演示例子:
executor.scheduleAtFixedRate(() -> {
try {
// 任務邏輯
} catch (Exception e) {
System.err.println("任務異常:" + e.getMessage());
}
}, 0, 1, TimeUnit.SECONDS);
選擇合適的排程方法
scheduleAtFixedRate
和scheduleWithFixedDelay
這兩種排程方式的主要區別在於任務的執行間隔計算方式,具體表現為是否會受到任務執行時間的影響。
scheduleAtFixedRate
- 嚴格的固定速率:它以固定的時間間隔排程任務,不考慮任務的實際執行時間。
-
時間點固定:假設初始延遲為
initialDelay
,任務的執行時間點為:
t0 = initialDelay
,
t1 = t0 + period
,
t2 = t1 + period
,以此類推。
適合任務執行時間較短(小於排程週期),並且對時間間隔有嚴格要求的場景。例如:
- 定期傳送心跳包。
- 週期性更新 UI 資料。
scheduleWithFixedDelay
- 任務完成後才開始計時:它會等待上一個任務完成後,再延遲設定的時間開始執行下一個任務。
- 動態間隔:執行時間和延遲時間之和決定了任務的執行頻率。
適合任務之間有依賴關係或任務執行時間不確定的場景。例如:
- 一個資料同步任務,需要等待上一次同步完全完成後,確保下一次同步不會衝突。
- 網路爬蟲任務,等待上一個爬取任務完成後再延遲執行。
時間線分析:
- 如果任務執行時間為 3 秒,延遲時間為 2 秒:
- 第一次任務在
t0=0
秒開始,t1=3
秒結束。 - 下一次任務從
t2=t1+2=5
秒開始,任務時間間隔為 5 秒。
- 第一次任務在
- 如果任務執行時間較短,比如 1 秒:
- 第一次任務在
t0=0
秒開始,t1=1
秒結束。 - 下一次任務從
t2=t1+2=3
秒開始,任務時間間隔為 3 秒。
- 第一次任務在
兩種排程方式的對比
特性 | scheduleAtFixedRate |
scheduleWithFixedDelay |
---|---|---|
執行時間間隔 | 固定(忽略任務執行時間) | 動態(任務完成後再計時) |
任務執行時間影響 | 可能出現堆積或延遲 | 不會堆積,保持任務之間的順序 |
適合場景 | 任務短小、需要嚴格時間間隔的任務 | 任務間有依賴、執行時間不確定的任務 |
總結
ScheduledThreadPoolExecutor
是 Java 開發中實現定時任務的強大工具。透過靈活的排程方式和多工管理,它為定時任務的開發帶來了極大的便利。在實際開發中,合理配置執行緒池大小、捕獲異常、選擇合適的排程方法,能夠幫助我們充分發揮其效能優勢。
對於複雜的定時任務,ScheduledThreadPoolExecutor
不僅提供了高效的併發處理能力,還能簡化任務排程邏輯,是 Java 併發程式設計的利器。
FunTester 原創精華
- 混沌工程、故障測試、Web 前端
- 服務端功能測試
- 效能測試專題
- Java、Groovy、Go
- 白盒、工具、爬蟲、UI 自動化
- 理論、感悟、影片