一文帶你學(xué)會Java中ScheduledThreadPoolExecutor使用
1. 概要
前面文章的地址:
定時/延時任務(wù)-自己實現(xiàn)一個簡單的定時器
ScheduledThreadPoolExecutor 是 Java 并發(fā)包 (java.util.concurrent) 中的一個類,同時也是 ThreadPoolExecutor 的一個子類,這就意味者 ScheduledThreadPoolExecutor 不像 Timer 中使用單個線程去執(zhí)行任務(wù),ScheduledThreadPoolExecutor 使用了線程池去執(zhí)行,同時 ScheduledThreadPoolExecutor 也具備了 Timer 中的各種功能。
2. 固定速率和固定延時
2.1 固定速率
固定速率 策略表示任務(wù)在固定的時間間隔內(nèi)重復(fù)執(zhí)行,不管任務(wù)的執(zhí)行時間有多長,如果任務(wù)的執(zhí)行時間超過了時間間隔,那么下一個任務(wù)會在當(dāng)前任務(wù)執(zhí)行完畢之后就會馬上開始執(zhí)行,下面是一個例子:假設(shè)我們設(shè)置了一個固定速率為 5 的任務(wù),從 0s 開始執(zhí)行,也就是說這個任務(wù) 5s 執(zhí)行一次:
- 第一次執(zhí)行: 在 0s 開始執(zhí)行一次,假設(shè)執(zhí)行時間是 3s
- 第二次執(zhí)行: 在 5s 開始執(zhí)行第二次,假設(shè)執(zhí)行的時候被阻塞了,執(zhí)行了 8s
- 第三次執(zhí)行: 在 13s 開始執(zhí)行第三次,假設(shè)執(zhí)行的時候被阻塞了,執(zhí)行了 3s
- 第四次執(zhí)行: 在 16s 開始執(zhí)行第四次,假設(shè)執(zhí)行的時候沒有被阻塞
- 第四次執(zhí)行: 在 20s 開始執(zhí)行第五次,假設(shè)執(zhí)行的時候沒有被阻塞
- …
2.2 固定延時
固定延時 策略表示任務(wù)在當(dāng)前任務(wù)執(zhí)行完成之后,固定延時一段時間再執(zhí)行下一個任務(wù),下面是一個例子:假設(shè)我們設(shè)置了一個固定速率為 5 的任務(wù),從 0s 開始執(zhí)行,也就是說這個任務(wù) 5s 執(zhí)行一次:
- 第一次執(zhí)行: 在 0s 開始執(zhí)行一次,假設(shè)執(zhí)行時間是 3s
- 第二次執(zhí)行: 在 8s 開始執(zhí)行第二次,假設(shè)執(zhí)行的時候被阻塞了,執(zhí)行了 8s
- 第三次執(zhí)行: 在 21s 開始執(zhí)行第三次,假設(shè)執(zhí)行的時候被阻塞了,執(zhí)行了 3s
- 第四次執(zhí)行: 在 29 開始執(zhí)行第四次,假設(shè)執(zhí)行的時候沒有被阻塞
- 第四次執(zhí)行: 在 34s 開始執(zhí)行第五次,假設(shè)執(zhí)行的時候沒有被阻塞
- …
上面的例子中其實固定速率的執(zhí)行和 Timer 是一樣的,但是固定延時有點(diǎn)不一樣,不知道你有沒有發(fā)現(xiàn),固定延時下執(zhí)行的時候每一個任務(wù)和前一個任務(wù)的時間間隔一定是 任務(wù)執(zhí)行時間 + 延時時間,其實相比于 Timer,ScheduledThreadPoolExecutor 的固定延時看起來更像是 “正宗” 一點(diǎn)的固定延時
3. API 解釋
3.1 schedule
還是老規(guī)矩,先看下 ScheduledThreadPoolExecutor 的幾個 API,看看用法是什么樣的,首先就是 schedule 方法,這個方法就是普通的延時方法,只執(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 也提供了一個 callable 類型的任務(wù)的實現(xiàn),使用 Callable 的好處就是可以獲取任務(wù)的實現(xiàn)返回值,如下例子所示:
public class Pra { public static void main(String[] args) { ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(16); // 創(chuàng)建一個 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 固定延時 - 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 啟動,然后執(zhí)行時間 3s
第二次任務(wù)在 2024-11-24 19:19:44:718 啟動,跟第一次任務(wù)相差 8s,延時 = 5s + 3s = 8s
第三次任務(wù)在 2024-11-24 19:19:52:741 啟動,跟第二次任務(wù)相差 8s,延時 = 5s + 3s = 8s
第四次任務(wù)在 2024-11-24 19:20:05:760 啟動,跟第三次任務(wù)相差 13s,延時 = 5s + 8s = 13s
第五次任務(wù)在 2024-11-24 19:20:13:773 啟動,跟第四次任務(wù)相差 8s,延時 = 5s + 3s = 8s
第六次任務(wù)在 2024-11-24 19:20:26:787 啟動,跟第五次任務(wù)相差 13s,延時 = 5s + 8s = 13s
…
可以看到 scheduleWithFixedDelay 的延時是相對于當(dāng)前時間 + 延時時間的,跟 Timer 的固定延時任務(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ù)會在當(dāng)前任務(wù)執(zhí)行完之后再添加下一次要執(zhí)行的任務(wù)
- 首先第一次任務(wù) 2024-11-24 16:23:25:762 啟動,然后執(zhí)行時間 8s,執(zhí)行完之后會添加一個 2024-11-24 16:23:30 s 執(zhí)行的任務(wù),當(dāng)前時間 2024-11-24 16:23:33 s
- 第二次任務(wù)在 2024-11-24 16:23:33:770 啟動,因為 2024-11-24 16:23:33:770 超過了 2024-11-24 16:23:30 s 這個時間點(diǎn), 所以立刻執(zhí)行,執(zhí)行時間 3s,執(zhí)行完之后往隊列里面添加一個 2024-11-24 16:23:35 s 執(zhí)行的任務(wù),當(dāng)前時間 2024-11-24 16:23:36 s
- 第二次任務(wù)在 2024-11-24 16:23:36:784 啟動,因為 2024-11-24 16:23:36:784 超過了 2024-11-24 16:23:35 s 這個時間點(diǎn), 所以立刻執(zhí)行,執(zhí)行時間 3s,執(zhí)行完之后往隊列里面添加一個 2024-11-24 16:23:40 s 執(zhí)行的任務(wù),當(dāng)前時間 2024-11-24 16:23:39 s
- 第四次任務(wù)在 2024-11-24 16:23:40:739 啟動,因為第三個任務(wù)執(zhí)行完之后時間是 2024-11-24 16:23:39 s,還沒有到第四個任務(wù)執(zhí)行的時間點(diǎn),這時候就等待,等到 2024-11-24 16:23:40:739,執(zhí)行時間 3s,執(zhí)行完之后往隊列里面添加一個 2024-11-24 16:23:45 s 執(zhí)行的任務(wù),執(zhí)行完的時間當(dāng)前是 2024-11-24 16:23:42 s
- 第五次任務(wù)在 2024-11-24 16:23:45:751 啟動,因為第四個任務(wù)執(zhí)行完之后時間是 2024-11-24 16:23:42 s,還沒有到第五個任務(wù)執(zhí)行的時間點(diǎn),這時候就等待,等到 2024-11-24 16:23:45:751,執(zhí)行時間 8s,執(zhí)行完之后往隊列里面添加一個 2024-11-24 16:23:50 s 執(zhí)行的任務(wù),執(zhí)行完的時間當(dāng)前是 2024-11-24 16:23:53 s
- 第六次任務(wù)在 2024-11-24 16:23:53:763 啟動,因為第六個任務(wù)執(zhí)行時間是 2024-11-24 16:23:50 s,而執(zhí)行完第五個任務(wù)已經(jīng)是 2024-11-24 16:23:53 s了,超過任務(wù)的執(zhí)行時間,所以會立馬執(zhí)行,執(zhí)行時間 3s,執(zhí)行完后往隊列里面添加一個 2024-11-24 16:23:55 s 的任務(wù),當(dāng)前時間是 2024-11-24 16:23:56 s
- …
其實上面的解釋中可以把后面的毫秒去掉,這樣比較方便理解
4. 小結(jié)
上面就是幾個 API 了,其中固定速率的和 Timer 的實現(xiàn)方式有點(diǎn)不同,后續(xù)文章中,我會對 ScheduledThreadPoolExecutor 的源碼進(jìn)行詳細(xì)的解析,當(dāng)然在解析 ScheduledThreadPoolExecutor 之前首先需要知道下線程池的工作原理和工作流程。
以上就是一文帶你學(xué)會Java中ScheduledThreadPoolExecutor使用的詳細(xì)內(nèi)容,更多關(guān)于Java ScheduledThreadPoolExecutor的資料請關(guān)注腳本之家其它相關(guān)文章!
- Java自帶定時任務(wù)ScheduledThreadPoolExecutor實現(xiàn)定時器和延時加載功能
- java 定時器線程池(ScheduledThreadPoolExecutor)的實現(xiàn)
- java高并發(fā)ScheduledThreadPoolExecutor與Timer區(qū)別
- 詳解Java ScheduledThreadPoolExecutor的踩坑與解決方法
- java高并發(fā)ScheduledThreadPoolExecutor類深度解析
- Java調(diào)度線程池ScheduledThreadPoolExecutor不執(zhí)行問題分析
- Java定時任務(wù)ScheduledThreadPoolExecutor示例詳解
- Java中的ScheduledThreadPoolExecutor定時任務(wù)詳解
相關(guān)文章
Spring boot中filter類不能注入@Autowired變量問題
這篇文章主要介紹了Spring boot中filter類不能注入@Autowired變量問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09Java開發(fā)中POJO和JSON互轉(zhuǎn)時如何忽略隱藏字段的問題
這篇文章主要介紹了Java開發(fā)中POJO和JSON互轉(zhuǎn)時如何忽略隱藏字段的問題,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-02-02java用LocalDateTime類獲取當(dāng)天時間、前一天時間及本周/本月的開始和結(jié)束時間
這篇文章主要給大家介紹了關(guān)于java使用LocalDateTime類獲取當(dāng)天時間、前一天時間及本周/本月的開始和結(jié)束時間的相關(guān)資料,文中通過代碼示例介紹的非常詳細(xì),需要的朋友可以參考下2023-08-08