一文帶你學(xué)會(huì)Java中ScheduledThreadPoolExecutor使用
1. 概要
前面文章的地址:
定時(shí)/延時(shí)任務(wù)-自己實(shí)現(xiàn)一個(gè)簡(jiǎn)單的定時(shí)器
ScheduledThreadPoolExecutor 是 Java 并發(fā)包 (java.util.concurrent) 中的一個(gè)類,同時(shí)也是 ThreadPoolExecutor 的一個(gè)子類,這就意味者 ScheduledThreadPoolExecutor 不像 Timer 中使用單個(gè)線程去執(zhí)行任務(wù),ScheduledThreadPoolExecutor 使用了線程池去執(zhí)行,同時(shí) ScheduledThreadPoolExecutor 也具備了 Timer 中的各種功能。
2. 固定速率和固定延時(shí)
2.1 固定速率
固定速率 策略表示任務(wù)在固定的時(shí)間間隔內(nèi)重復(fù)執(zhí)行,不管任務(wù)的執(zhí)行時(shí)間有多長(zhǎng),如果任務(wù)的執(zhí)行時(shí)間超過了時(shí)間間隔,那么下一個(gè)任務(wù)會(huì)在當(dāng)前任務(wù)執(zhí)行完畢之后就會(huì)馬上開始執(zhí)行,下面是一個(gè)例子:假設(shè)我們?cè)O(shè)置了一個(gè)固定速率為 5 的任務(wù),從 0s 開始執(zhí)行,也就是說這個(gè)任務(wù) 5s 執(zhí)行一次:
- 第一次執(zhí)行: 在 0s 開始執(zhí)行一次,假設(shè)執(zhí)行時(shí)間是 3s
- 第二次執(zhí)行: 在 5s 開始執(zhí)行第二次,假設(shè)執(zhí)行的時(shí)候被阻塞了,執(zhí)行了 8s
- 第三次執(zhí)行: 在 13s 開始執(zhí)行第三次,假設(shè)執(zhí)行的時(shí)候被阻塞了,執(zhí)行了 3s
- 第四次執(zhí)行: 在 16s 開始執(zhí)行第四次,假設(shè)執(zhí)行的時(shí)候沒有被阻塞
- 第四次執(zhí)行: 在 20s 開始執(zhí)行第五次,假設(shè)執(zhí)行的時(shí)候沒有被阻塞
- …
2.2 固定延時(shí)
固定延時(shí) 策略表示任務(wù)在當(dāng)前任務(wù)執(zhí)行完成之后,固定延時(shí)一段時(shí)間再執(zhí)行下一個(gè)任務(wù),下面是一個(gè)例子:假設(shè)我們?cè)O(shè)置了一個(gè)固定速率為 5 的任務(wù),從 0s 開始執(zhí)行,也就是說這個(gè)任務(wù) 5s 執(zhí)行一次:
- 第一次執(zhí)行: 在 0s 開始執(zhí)行一次,假設(shè)執(zhí)行時(shí)間是 3s
- 第二次執(zhí)行: 在 8s 開始執(zhí)行第二次,假設(shè)執(zhí)行的時(shí)候被阻塞了,執(zhí)行了 8s
- 第三次執(zhí)行: 在 21s 開始執(zhí)行第三次,假設(shè)執(zhí)行的時(shí)候被阻塞了,執(zhí)行了 3s
- 第四次執(zhí)行: 在 29 開始執(zhí)行第四次,假設(shè)執(zhí)行的時(shí)候沒有被阻塞
- 第四次執(zhí)行: 在 34s 開始執(zhí)行第五次,假設(shè)執(zhí)行的時(shí)候沒有被阻塞
- …
上面的例子中其實(shí)固定速率的執(zhí)行和 Timer 是一樣的,但是固定延時(shí)有點(diǎn)不一樣,不知道你有沒有發(fā)現(xiàn),固定延時(shí)下執(zhí)行的時(shí)候每一個(gè)任務(wù)和前一個(gè)任務(wù)的時(shí)間間隔一定是 任務(wù)執(zhí)行時(shí)間 + 延時(shí)時(shí)間,其實(shí)相比于 Timer,ScheduledThreadPoolExecutor 的固定延時(shí)看起來更像是 “正宗” 一點(diǎn)的固定延時(shí)
3. API 解釋
3.1 schedule
還是老規(guī)矩,先看下 ScheduledThreadPoolExecutor 的幾個(gè) API,看看用法是什么樣的,首先就是 schedule 方法,這個(gè)方法就是普通的延時(shí)方法,只執(zhí)行一次,非周期調(diào)度
public class Pra {
public static void main(String[] args) {
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(16);
Thread thread = new Thread(() -> {
try {
System.out.println("Thread-Current: " + Thread.currentThread().getName() + ", time = " + getTime());
if (Math.random() < 0.5) {
System.out.println("sleep: 3s");
Thread.sleep(3000);
} else {
System.out.println("sleep: 8s");
Thread.sleep(8000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "thread-executor-run");
executor.schedule(thread, 0, TimeUnit.SECONDS);
}
private static String getTime() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
return sdf.format(new Date());
}
}
執(zhí)行結(jié)果如下所示,延遲 0s 就開始執(zhí)行任務(wù)

ScheduledThreadPoolExecutor 也提供了一個(gè) callable 類型的任務(wù)的實(shí)現(xiàn),使用 Callable 的好處就是可以獲取任務(wù)的實(shí)現(xiàn)返回值,如下例子所示:
public class Pra {
public static void main(String[] args) {
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(16);
// 創(chuàng)建一個(gè) Callable 任務(wù)
Callable<Integer> task = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("Task is running on thread: " + Thread.currentThread().getName());
return 1; // 返回結(jié)果
}
};
// 調(diào)度 Callable 任務(wù),在 0 秒后執(zhí)行
ScheduledFuture<Integer> future = executor.schedule(task, 0, TimeUnit.SECONDS);
try {
// 獲取任務(wù)的結(jié)果
Integer result = future.get();
System.out.println("Task result: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
private static String getTime() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
return sdf.format(new Date());
}
}
輸出結(jié)果如下所示:

3.2 固定延時(shí) - scheduleWithFixedDelay
public class Pra {
public static void main(String[] args) {
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(16);
Thread thread = new Thread(() -> {
try {
System.out.println("Thread-Current: " + Thread.currentThread().getName() + ", time = " + getTime());
if (Math.random() < 0.5) {
System.out.println("sleep: 3s");
Thread.sleep(3000);
} else {
System.out.println("sleep: 8s");
Thread.sleep(8000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "thread-executor-run");
executor.scheduleWithFixedDelay(thread, 0, 5, TimeUnit.SECONDS);
}
private static String getTime() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
return sdf.format(new Date());
}
}
輸入如下所示:

來分析一下:
首先第一次任務(wù) 2024-11-24 19:19:36:698 啟動(dòng),然后執(zhí)行時(shí)間 3s
第二次任務(wù)在 2024-11-24 19:19:44:718 啟動(dòng),跟第一次任務(wù)相差 8s,延時(shí) = 5s + 3s = 8s
第三次任務(wù)在 2024-11-24 19:19:52:741 啟動(dòng),跟第二次任務(wù)相差 8s,延時(shí) = 5s + 3s = 8s
第四次任務(wù)在 2024-11-24 19:20:05:760 啟動(dòng),跟第三次任務(wù)相差 13s,延時(shí) = 5s + 8s = 13s
第五次任務(wù)在 2024-11-24 19:20:13:773 啟動(dòng),跟第四次任務(wù)相差 8s,延時(shí) = 5s + 3s = 8s
第六次任務(wù)在 2024-11-24 19:20:26:787 啟動(dòng),跟第五次任務(wù)相差 13s,延時(shí) = 5s + 8s = 13s
…
可以看到 scheduleWithFixedDelay 的延時(shí)是相對(duì)于當(dāng)前時(shí)間 + 延時(shí)時(shí)間的,跟 Timer 的固定延時(shí)任務(wù)有點(diǎn)不同
3.2 固定速率 - scheduleWithFixedDelay
public class Pra {
public static void main(String[] args) {
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(16);
Thread thread = new Thread(() -> {
try {
System.out.println("Thread-Current: " + Thread.currentThread().getName() + ", time = " + getTime());
if (Math.random() < 0.5) {
System.out.println("sleep: 3s");
Thread.sleep(3000);
} else {
System.out.println("sleep: 8s");
Thread.sleep(8000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "thread-executor-run");
executor.scheduleAtFixedRate(thread, 0, 5, TimeUnit.SECONDS);
}
private static String getTime() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
return sdf.format(new Date());
}
}
輸出結(jié)果如下:

來分析下上面的執(zhí)行流程,需要注意的是,ScheduledThreadPoolExecutor 的固定速率任務(wù)會(huì)在當(dāng)前任務(wù)執(zhí)行完之后再添加下一次要執(zhí)行的任務(wù)
- 首先第一次任務(wù) 2024-11-24 16:23:25:762 啟動(dòng),然后執(zhí)行時(shí)間 8s,執(zhí)行完之后會(huì)添加一個(gè) 2024-11-24 16:23:30 s 執(zhí)行的任務(wù),當(dāng)前時(shí)間 2024-11-24 16:23:33 s
- 第二次任務(wù)在 2024-11-24 16:23:33:770 啟動(dòng),因?yàn)?2024-11-24 16:23:33:770 超過了 2024-11-24 16:23:30 s 這個(gè)時(shí)間點(diǎn), 所以立刻執(zhí)行,執(zhí)行時(shí)間 3s,執(zhí)行完之后往隊(duì)列里面添加一個(gè) 2024-11-24 16:23:35 s 執(zhí)行的任務(wù),當(dāng)前時(shí)間 2024-11-24 16:23:36 s
- 第二次任務(wù)在 2024-11-24 16:23:36:784 啟動(dòng),因?yàn)?2024-11-24 16:23:36:784 超過了 2024-11-24 16:23:35 s 這個(gè)時(shí)間點(diǎn), 所以立刻執(zhí)行,執(zhí)行時(shí)間 3s,執(zhí)行完之后往隊(duì)列里面添加一個(gè) 2024-11-24 16:23:40 s 執(zhí)行的任務(wù),當(dāng)前時(shí)間 2024-11-24 16:23:39 s
- 第四次任務(wù)在 2024-11-24 16:23:40:739 啟動(dòng),因?yàn)榈谌齻€(gè)任務(wù)執(zhí)行完之后時(shí)間是 2024-11-24 16:23:39 s,還沒有到第四個(gè)任務(wù)執(zhí)行的時(shí)間點(diǎn),這時(shí)候就等待,等到 2024-11-24 16:23:40:739,執(zhí)行時(shí)間 3s,執(zhí)行完之后往隊(duì)列里面添加一個(gè) 2024-11-24 16:23:45 s 執(zhí)行的任務(wù),執(zhí)行完的時(shí)間當(dāng)前是 2024-11-24 16:23:42 s
- 第五次任務(wù)在 2024-11-24 16:23:45:751 啟動(dòng),因?yàn)榈谒膫€(gè)任務(wù)執(zhí)行完之后時(shí)間是 2024-11-24 16:23:42 s,還沒有到第五個(gè)任務(wù)執(zhí)行的時(shí)間點(diǎn),這時(shí)候就等待,等到 2024-11-24 16:23:45:751,執(zhí)行時(shí)間 8s,執(zhí)行完之后往隊(duì)列里面添加一個(gè) 2024-11-24 16:23:50 s 執(zhí)行的任務(wù),執(zhí)行完的時(shí)間當(dāng)前是 2024-11-24 16:23:53 s
- 第六次任務(wù)在 2024-11-24 16:23:53:763 啟動(dòng),因?yàn)榈诹鶄€(gè)任務(wù)執(zhí)行時(shí)間是 2024-11-24 16:23:50 s,而執(zhí)行完第五個(gè)任務(wù)已經(jīng)是 2024-11-24 16:23:53 s了,超過任務(wù)的執(zhí)行時(shí)間,所以會(huì)立馬執(zhí)行,執(zhí)行時(shí)間 3s,執(zhí)行完后往隊(duì)列里面添加一個(gè) 2024-11-24 16:23:55 s 的任務(wù),當(dāng)前時(shí)間是 2024-11-24 16:23:56 s
- …
其實(shí)上面的解釋中可以把后面的毫秒去掉,這樣比較方便理解
4. 小結(jié)
上面就是幾個(gè) API 了,其中固定速率的和 Timer 的實(shí)現(xiàn)方式有點(diǎn)不同,后續(xù)文章中,我會(huì)對(duì) ScheduledThreadPoolExecutor 的源碼進(jìn)行詳細(xì)的解析,當(dāng)然在解析 ScheduledThreadPoolExecutor 之前首先需要知道下線程池的工作原理和工作流程。
以上就是一文帶你學(xué)會(huì)Java中ScheduledThreadPoolExecutor使用的詳細(xì)內(nèi)容,更多關(guān)于Java ScheduledThreadPoolExecutor的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Java自帶定時(shí)任務(wù)ScheduledThreadPoolExecutor實(shí)現(xiàn)定時(shí)器和延時(shí)加載功能
- java 定時(shí)器線程池(ScheduledThreadPoolExecutor)的實(shí)現(xiàn)
- java高并發(fā)ScheduledThreadPoolExecutor與Timer區(qū)別
- 詳解Java ScheduledThreadPoolExecutor的踩坑與解決方法
- java高并發(fā)ScheduledThreadPoolExecutor類深度解析
- Java調(diào)度線程池ScheduledThreadPoolExecutor不執(zhí)行問題分析
- Java定時(shí)任務(wù)ScheduledThreadPoolExecutor示例詳解
- Java中的ScheduledThreadPoolExecutor定時(shí)任務(wù)詳解
相關(guān)文章
SpringMVC實(shí)現(xiàn)賬號(hào)只能在一處登陸
這篇文章主要為大家詳細(xì)介紹了SpringMVC如何實(shí)現(xiàn)賬號(hào)只能在一處登陸,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03
基于hibernate實(shí)現(xiàn)的分頁技術(shù)實(shí)例分析
這篇文章主要介紹了基于hibernate實(shí)現(xiàn)的分頁技術(shù),結(jié)合實(shí)例形式分析了Hibernate分頁技術(shù)的原理,實(shí)現(xiàn)步驟與相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2016-03-03
Java21增強(qiáng)對(duì)Emoji表情符號(hào)處理示例詳解
這篇文章主要為大家介紹了Java21增強(qiáng)對(duì)Emoji表情符號(hào)處理示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11
記一次集成swagger2(Knife4j)在線文檔提示:Knude4j文檔請(qǐng)求異常的解決辦法
Knife4j是一個(gè)集Swagger2 和 OpenAPI3為一體的增強(qiáng)解決方案,下面這篇文章主要給大家介紹了關(guān)于一次集成swagger2(Knife4j)在線文檔提示:Knude4j文檔請(qǐng)求異常的解決辦法,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-02-02
Java實(shí)現(xiàn)批量查找與替換Excel文本的思路詳解
在 Java 中,可以通過find和replace的方法來查找和替換單元格的數(shù)據(jù),下面小編將以Excel文件為例為大家介紹如何實(shí)現(xiàn)Excel文件內(nèi)容的批量替換,感興趣的朋友跟隨小編一起看看吧2023-10-10
SpringBoot實(shí)現(xiàn)應(yīng)用開機(jī)自啟動(dòng)與進(jìn)程守護(hù)配置方案
機(jī)房意外斷電,恢復(fù)供電后所有服務(wù)器重啟,但SpringBoot應(yīng)用沒有自動(dòng)啟動(dòng),事后排查發(fā)現(xiàn),沒有留下開機(jī)自啟動(dòng)配置文檔,所以本文整理了兩套SpringBoot應(yīng)用開機(jī)自啟動(dòng)與進(jìn)程守護(hù)方案,需要的朋友可以參考下2025-07-07
Idea跑的項(xiàng)目沒問題將程序install成jar包運(yùn)行報(bào)錯(cuò)空指針的問題
這篇文章主要介紹了Idea跑的項(xiàng)目沒問題,將程序install成jar包運(yùn)行報(bào)錯(cuò)空指針的問題,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06

