一文搞懂Java?ScheduledExecutorService的使用
JUC包(java.util.concurrent)中提供了對定時任務(wù)的支持,即ScheduledExecutorService接口。
本文對ScheduledExecutorService的介紹,將基于Timer類使用介紹進(jìn)行,因此請先閱讀Timer類使用介紹文章。
此處為語雀內(nèi)容卡片,點擊鏈接查看
一、創(chuàng)建ScheduledExecutorService對象
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
二、ScheduledExecutorService方法
ScheduledExecutorService實現(xiàn)了ExecutorService接口,ExecutorService接口中的方法事實上屬于線程池相關(guān)的一般方法,不在本文討論。
ScheduledExecutorService本身提供了以下4個方法:
- ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit):延遲delay單位時間后,執(zhí)行一次任務(wù)
- <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit):延遲delay單位時間后,執(zhí)行一次任務(wù)
- ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit):延遲initialDelay單位時間后,執(zhí)行一次任務(wù),之后每隔period單位時間執(zhí)行一次任務(wù)(固定速率)
- ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit):延遲initialDelay單位時間后,執(zhí)行一次任務(wù),之后每隔period單位時間執(zhí)行一次任務(wù)(固定延時)
ScheduledExecutorService和Timer進(jìn)行對比,兩者所提供的方法是類似的,區(qū)別在于Timer有提供指定時間點執(zhí)行任務(wù),而ScheduledExecutorService沒有提供。
Timer提供的方法返回值均為void,而ScheduledExecutorService的方法返回值均為ScheduledFuture(繼承于Future接口)。
三、固定速率和固定延時的區(qū)別
和Timer一樣,我們用示例來展示ScheduledExecutorService固定速率和固定延時的區(qū)別,并與Timer進(jìn)行對比。
1. 固定速率
示例:
System.out.println("啟動于:" + DateUtil.formatNow()); ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5); executorService.scheduleAtFixedRate( new Runnable() { int i = 1; @Override public void run() { System.out.print(i + " " + DateUtil.formatNow() + " 開始執(zhí)行, "); if(i == 3) { ThreadUtil.sleep(11 * 1000); } System.out.println(DateUtil.formatNow() + " 結(jié)束"); i ++; } }, 5, 2, TimeUnit.SECONDS);
輸出:
啟動于:2022-10-31 17:15:44
1 2022-10-31 17:15:49 開始執(zhí)行, 2022-10-31 17:15:49 結(jié)束
2 2022-10-31 17:15:51 開始執(zhí)行, 2022-10-31 17:15:51 結(jié)束
3 2022-10-31 17:15:53 開始執(zhí)行, 2022-10-31 17:16:04 結(jié)束 *
4 2022-10-31 17:16:04 開始執(zhí)行, 2022-10-31 17:16:04 結(jié)束 *
5 2022-10-31 17:16:04 開始執(zhí)行, 2022-10-31 17:16:04 結(jié)束 *
6 2022-10-31 17:16:04 開始執(zhí)行, 2022-10-31 17:16:04 結(jié)束 *
7 2022-10-31 17:16:04 開始執(zhí)行, 2022-10-31 17:16:04 結(jié)束 *
8 2022-10-31 17:16:04 開始執(zhí)行, 2022-10-31 17:16:04 結(jié)束 *
9 2022-10-31 17:16:05 開始執(zhí)行, 2022-10-31 17:16:05 結(jié)束
10 2022-10-31 17:16:07 開始執(zhí)行, 2022-10-31 17:16:07 結(jié)束
11 2022-10-31 17:16:09 開始執(zhí)行, 2022-10-31 17:16:09 結(jié)束
沒有11秒耗時的情況下,正常應(yīng)該是輸出:
啟動于:2022-10-31 17:15:44
1 2022-10-31 17:15:49 開始執(zhí)行, 2022-10-31 17:15:49 結(jié)束
2 2022-10-31 17:15:51 開始執(zhí)行, 2022-10-31 17:15:51 結(jié)束
3 2022-10-31 17:15:53 開始執(zhí)行, 2022-10-31 17:15:53 結(jié)束
4 2022-10-31 17:15:55 開始執(zhí)行, 2022-10-31 17:15:55 結(jié)束
5 2022-10-31 17:15:57 開始執(zhí)行, 2022-10-31 17:15:57 結(jié)束
6 2022-10-31 17:15:59 開始執(zhí)行, 2022-10-31 17:15:59 結(jié)束
7 2022-10-31 17:16:01 開始執(zhí)行, 2022-10-31 17:16:01 結(jié)束
8 2022-10-31 17:16:03 開始執(zhí)行, 2022-10-31 17:16:03 結(jié)束
9 2022-10-31 17:16:05 開始執(zhí)行, 2022-10-31 17:16:05 結(jié)束
10 2022-10-31 17:16:07 開始執(zhí)行, 2022-10-31 17:16:07 結(jié)束
11 2022-10-31 17:16:09 開始執(zhí)行, 2022-10-31 17:16:09 結(jié)束
從測試結(jié)果中可以看出,當(dāng)有一次任務(wù)執(zhí)行耗時過長,超出了設(shè)定的period時間單位,將會影響后續(xù)5次任務(wù)準(zhǔn)時執(zhí)行,當(dāng)耗時任務(wù)完成后,ScheduledExecutorService將會立即將延誤的5次任務(wù)一起補(bǔ)上,并保障后續(xù)的任務(wù)按預(yù)期的時間點執(zhí)行。
這與ScheduledExecutorService固定速率的效果與Timer是完全一樣的,讀者可直接參考Timer的固定速率介紹。
2. 固定延時
示例:
System.out.println("啟動于:" + DateUtil.formatNow()); ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5); executorService.scheduleWithFixedDelay( new Runnable() { int i = 1; @Override public void run() { System.out.print(i + " " + DateUtil.formatNow() + " 開始執(zhí)行, "); if(i == 3) { ThreadUtil.sleep(11 * 1000); } System.out.println(DateUtil.formatNow() + " 結(jié)束"); i ++; } }, 5, 2, TimeUnit.SECONDS);
輸出:
1 2022-10-31 17:16:41 開始執(zhí)行, 2022-10-31 17:16:41 結(jié)束
2 2022-10-31 17:16:43 開始執(zhí)行, 2022-10-31 17:16:43 結(jié)束
3 2022-10-31 17:16:45 開始執(zhí)行, 2022-10-31 17:16:56 結(jié)束 *
4 2022-10-31 17:16:58 開始執(zhí)行, 2022-10-31 17:16:58 結(jié)束
5 2022-10-31 17:17:00 開始執(zhí)行, 2022-10-31 17:17:00 結(jié)束
6 2022-10-31 17:17:02 開始執(zhí)行, 2022-10-31 17:17:02 結(jié)束
7 2022-10-31 17:17:04 開始執(zhí)行, 2022-10-31 17:17:04 結(jié)束
8 2022-10-31 17:17:06 開始執(zhí)行, 2022-10-31 17:17:06 結(jié)束
9 2022-10-31 17:17:08 開始執(zhí)行, 2022-10-31 17:17:08 結(jié)束
沒有11秒耗時的情況下,正常應(yīng)該是輸出:
1 2022-10-31 17:16:41 開始執(zhí)行, 2022-10-31 17:16:41 結(jié)束
2 2022-10-31 17:16:43 開始執(zhí)行, 2022-10-31 17:16:43 結(jié)束
3 2022-10-31 17:16:45 開始執(zhí)行, 2022-10-31 17:16:45 結(jié)束
4 2022-10-31 17:16:47 開始執(zhí)行, 2022-10-31 17:16:47 結(jié)束
5 2022-10-31 17:16:49 開始執(zhí)行, 2022-10-31 17:16:49 結(jié)束
6 2022-10-31 17:16:51 開始執(zhí)行, 2022-10-31 17:16:51 結(jié)束
7 2022-10-31 17:16:53 開始執(zhí)行, 2022-10-31 17:16:53 結(jié)束
8 2022-10-31 17:16:55 開始執(zhí)行, 2022-10-31 17:16:55 結(jié)束
9 2022-10-31 17:16:57 開始執(zhí)行, 2022-10-31 17:16:57 結(jié)束
固定延時是當(dāng)任務(wù)執(zhí)行耗時過長,超出設(shè)定的delay時間單位,后續(xù)的任務(wù)將會被順延推遲,這個設(shè)計是與Timer一樣的,但與Timer卻有一點小區(qū)別。
在Timer類使用介紹中,曾提到Timer類固定延時下與我想象的不太一致,Timer在第3次任務(wù)執(zhí)行完成后會立即執(zhí)行第4次任務(wù),接著才是間隔2秒執(zhí)行第5次任務(wù)。
而ScheduledExecutorService則與我的想象完全一致,當(dāng)?shù)?次任務(wù)執(zhí)行完成后,會間隔2秒再執(zhí)行第4次任務(wù)。
所以固定延時下,Timer和ScheduledExecutorService的實現(xiàn)是有一點區(qū)別的。
四、調(diào)度多個任務(wù)
在Timer中,一個TimerTask對象是一個任務(wù)。
而在ScheduledExecutorService中,則一個Runnable對象一個任務(wù)。
第三節(jié)介紹的是固定速率和固定延時是如何影響一個可重復(fù)執(zhí)行任務(wù)(一個Runnable對象)的多次執(zhí)行的。
而本節(jié)介紹的是ScheduledExecutorService如何同時調(diào)度多個可重復(fù)執(zhí)行任務(wù)的。
與Timer內(nèi)部僅1個線程不同,ScheduledExecutorService內(nèi)部采用的是線程池,是支持自己設(shè)定線程數(shù)的。
那么理論上來說,如果要加入2個任務(wù),ScheduledExecutorService設(shè)定線程數(shù)為2,就不會出現(xiàn)相互影響的情況。
我們來驗證一下。
定義任務(wù),當(dāng)執(zhí)行第3次時將會休眠11秒:
class Task implements Runnable { private int i = 1; private String name; public Task(String name) { this.name = name; } @Override public void run() { System.out.println(i + " " + name + ":" + DateUtil.formatNow() + " 開始執(zhí)行"); if(i == 3) { ThreadUtil.sleep(11 * 1000); } System.out.println(i + " " + name + ":" + DateUtil.formatNow() + " 執(zhí)行結(jié)束"); i ++; } }
使用ScheduledExecutorService進(jìn)行調(diào)度:
System.out.println("啟動于:" + DateUtil.formatNow()); ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2); Task task1 = new Task("task1"); Task task2 = new Task("task2"); executorService.scheduleWithFixedDelay(task1, 5, 2, TimeUnit.SECONDS); executorService.scheduleWithFixedDelay(task2, 5, 2, TimeUnit.SECONDS);
由于控制臺輸出時,task1和task2的日志會混在一起,不容易閱讀,我這邊將task1和task2的日志分開。
task1日志:
啟動于:2022-10-31 17:49:51
1 task1:2022-10-31 17:49:56 開始執(zhí)行
1 task1:2022-10-31 17:49:56 執(zhí)行結(jié)束
2 task1:2022-10-31 17:49:58 開始執(zhí)行
2 task1:2022-10-31 17:49:58 執(zhí)行結(jié)束
3 task1:2022-10-31 17:50:00 開始執(zhí)行
3 task1:2022-10-31 17:50:11 執(zhí)行結(jié)束
4 task1:2022-10-31 17:50:13 開始執(zhí)行
4 task1:2022-10-31 17:50:13 執(zhí)行結(jié)束
5 task1:2022-10-31 17:50:15 開始執(zhí)行
5 task1:2022-10-31 17:50:15 執(zhí)行結(jié)束
task2日志:
啟動于:2022-10-31 17:49:51
1 task2:2022-10-31 17:49:56 開始執(zhí)行
1 task2:2022-10-31 17:49:56 執(zhí)行結(jié)束
2 task2:2022-10-31 17:49:58 開始執(zhí)行
2 task2:2022-10-31 17:49:58 執(zhí)行結(jié)束
3 task2:2022-10-31 17:50:00 開始執(zhí)行
3 task2:2022-10-31 17:50:11 執(zhí)行結(jié)束
4 task2:2022-10-31 17:50:13 開始執(zhí)行
4 task2:2022-10-31 17:50:13 執(zhí)行結(jié)束
5 task2:2022-10-31 17:50:15 開始執(zhí)行
經(jīng)過測試可以確定,當(dāng)加入的任務(wù)數(shù)不超過線程池線程數(shù)時,即使任務(wù)存在耗時也不會相互影響,而僅是影響自身任務(wù)下一次執(zhí)行的時間點。
那如果加入任務(wù)數(shù)超出了線程數(shù)呢?
我們測試一下加入3個任務(wù),線程數(shù)仍然為2.
Task task1 = new Task("task1"); Task task2 = new Task("task2"); Task task3 = new Task("task3"); executorService.scheduleWithFixedDelay(task1, 5, 2, TimeUnit.SECONDS); executorService.scheduleWithFixedDelay(task2, 5, 2, TimeUnit.SECONDS); executorService.scheduleWithFixedDelay(task3, 5, 2, TimeUnit.SECONDS);
將三個任務(wù)的日志分開展示。
task1:
啟動于:2022-10-31 17:53:22
1 task1:2022-10-31 17:53:27 開始執(zhí)行
1 task1:2022-10-31 17:53:27 執(zhí)行結(jié)束
2 task1:2022-10-31 17:53:29 開始執(zhí)行
2 task1:2022-10-31 17:53:29 執(zhí)行結(jié)束
3 task1:2022-10-31 17:53:31 開始執(zhí)行
3 task1:2022-10-31 17:53:42 執(zhí)行結(jié)束
4 task1:2022-10-31 17:53:44 開始執(zhí)行
4 task1:2022-10-31 17:53:44 執(zhí)行結(jié)束
5 task1:2022-10-31 17:53:46 開始執(zhí)行
5 task1:2022-10-31 17:53:46 執(zhí)行結(jié)束
6 task1:2022-10-31 17:53:48 開始執(zhí)行
6 task1:2022-10-31 17:53:48 執(zhí)行結(jié)束
7 task1:2022-10-31 17:53:50 開始執(zhí)行
7 task1:2022-10-31 17:53:50 執(zhí)行結(jié)束
8 task1:2022-10-31 17:53:52 開始執(zhí)行
8 task1:2022-10-31 17:53:52 執(zhí)行結(jié)束
9 task1:2022-10-31 17:53:54 開始執(zhí)行
9 task1:2022-10-31 17:53:54 執(zhí)行結(jié)束
10 task1:2022-10-31 17:53:56 開始執(zhí)行
10 task1:2022-10-31 17:53:56 執(zhí)行結(jié)束
task2:
啟動于:2022-10-31 17:53:22
1 task2:2022-10-31 17:53:27 開始執(zhí)行
1 task2:2022-10-31 17:53:27 執(zhí)行結(jié)束
2 task2:2022-10-31 17:53:29 開始執(zhí)行
2 task2:2022-10-31 17:53:29 執(zhí)行結(jié)束
3 task2:2022-10-31 17:53:31 開始執(zhí)行
3 task2:2022-10-31 17:53:42 執(zhí)行結(jié)束
4 task2:2022-10-31 17:53:44 開始執(zhí)行
4 task2:2022-10-31 17:53:44 執(zhí)行結(jié)束
5 task2:2022-10-31 17:53:46 開始執(zhí)行
5 task2:2022-10-31 17:53:46 執(zhí)行結(jié)束
6 task2:2022-10-31 17:53:48 開始執(zhí)行
6 task2:2022-10-31 17:53:48 執(zhí)行結(jié)束
7 task2:2022-10-31 17:53:50 開始執(zhí)行
7 task2:2022-10-31 17:53:50 執(zhí)行結(jié)束
8 task2:2022-10-31 17:53:52 開始執(zhí)行
8 task2:2022-10-31 17:53:52 執(zhí)行結(jié)束
9 task2:2022-10-31 17:53:54 開始執(zhí)行
9 task2:2022-10-31 17:53:54 執(zhí)行結(jié)束
10 task2:2022-10-31 17:53:56 開始執(zhí)行
10 task2:2022-10-31 17:53:56 執(zhí)行結(jié)束
task3:
啟動于:2022-10-31 17:53:22
1 task3:2022-10-31 17:53:27 開始執(zhí)行
1 task3:2022-10-31 17:53:27 執(zhí)行結(jié)束
2 task3:2022-10-31 17:53:29 開始執(zhí)行
2 task3:2022-10-31 17:53:29 執(zhí)行結(jié)束
3 task3:2022-10-31 17:53:42 開始執(zhí)行
3 task3:2022-10-31 17:53:53 執(zhí)行結(jié)束
4 task3:2022-10-31 17:53:55 開始執(zhí)行
4 task3:2022-10-31 17:53:55 執(zhí)行結(jié)束
5 task3:2022-10-31 17:53:57 開始執(zhí)行
5 task3:2022-10-31 17:53:57 執(zhí)行結(jié)束
從以上日志可以看出,task1和task2執(zhí)行是正常的,但是task3從第3次執(zhí)行開始出現(xiàn)錯誤。
task3第三次時間點正確時間應(yīng)該是17:53:31,而實際上被推遲到了17:53:42才開始。
從這點我們可以推測出,當(dāng)時2個線程都在執(zhí)行task1、task2的耗時11秒的第3次任務(wù),導(dǎo)致task3被推遲。
因此,我們在使用ScheduledExecutorService調(diào)度多個任務(wù)時,應(yīng)注意盡可能縮短任務(wù)的處理耗時,以及避免任務(wù)數(shù)超出線程數(shù)。
五、其他要點
任務(wù)執(zhí)行過程中拋出異常會發(fā)生什么情況?
Timer內(nèi)部是單個線程處理所有任務(wù),當(dāng)拋出異常時,Timer線程將終止運(yùn)行;
ScheduledExecutorService內(nèi)部是一個線程池,當(dāng)拋出異常時,此任務(wù)所在線程將會終止運(yùn)行被回收,該任務(wù)后續(xù)無法再觸發(fā)執(zhí)行,其他線程不受影響,因此編寫任務(wù)執(zhí)行代碼要注意捕獲異常。
以上就是一文搞懂Java ScheduledExecutorService的使用的詳細(xì)內(nèi)容,更多關(guān)于Java ScheduledExecutorService的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java中的三元運(yùn)算(三目運(yùn)算)以后用得到!
Java提供了一個三元運(yùn)算符,可以同時操作3個表達(dá)式,下面這篇文章主要給大家介紹了關(guān)于Java中三元運(yùn)算(三目運(yùn)算)的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-10-10Java線程讓步_動力節(jié)點Java學(xué)院整理
yield()的作用是讓步。它能讓當(dāng)前線程由“運(yùn)行狀態(tài)”進(jìn)入到“就緒狀態(tài)”,從而讓其它具有相同優(yōu)先級的等待線程獲取執(zhí)行權(quán)。下面通過本文給大家介紹Java線程讓步的相關(guān)知識,需要的朋友參考下吧2017-05-05SpringBoot實現(xiàn)HTTP服務(wù)監(jiān)聽的代碼示例
前后端分離項目中,在調(diào)用接口調(diào)試時候,我們可以通過cpolar內(nèi)網(wǎng)穿透將本地服務(wù)端接口模擬公共網(wǎng)絡(luò)環(huán)境遠(yuǎn)程調(diào)用調(diào)試,本次教程我們以Java服務(wù)端接口為例,需要的朋友可以參考下2023-05-05