详解ScheduledExecutorService的周期性执行方法
2019獨角獸企業(yè)重金招聘Python工程師標準>>>
詳解 ScheduledExecutorService 的周期性執(zhí)行方法
在最近的工作中,需要實現一個當一個任務執(zhí)行完后,再等 100 毫秒然后再次執(zhí)行的功能。當時最先反映到的就是 java 線程池的 ScheduledExecutorService,而 ScheduledExecutorService 有兩個周期性執(zhí)行任務的方法,分別是 scheduleAtFixedRate 與 scheduleWithFixedDelay,當時對這兩個方法也不大了解,感覺和我的理解有所偏差,所以對這兩個方法進行了研究。
ScheduledExecutorService 的基本原理
想要了解 scheduleWithFixedDelay 和 scheduleAtFixedRate 這兩個周期性執(zhí)行任務的方法,首先要了解 ScheduledExecutorService 的原理。在《java 并發(fā)編程的藝術》一書中有詳細的解說,這里就簡單的闡述一下。
ScheduledExecutorService 與其他線程池的區(qū)別,主要在于在執(zhí)行前將任務封裝為ScheduledFutureTask與其使用的阻塞隊列DelayedWorkQueue。
ScheduledFutureTask
private class ScheduledFutureTask<V>extends FutureTask<V> implements RunnableScheduledFuture<V> {/** 表示這個任務添加到ScheduledExecutorService中的序號 */private final long sequenceNumber;/** T表示這個任務將要被執(zhí)行的具體時間(時間戳) */private long time;/*** 表示任務執(zhí)行的間隔周期,若為0則表示不是周期性執(zhí)行任務*/private final long period;/*省略以下代碼*/}DelayedWorkQueue
DelayedWorkQueue 是一個優(yōu)先隊列,在元素出隊時,ScheduledFutureTask 的 time 最小的元素將優(yōu)先出隊,如果 time 值相同則判斷 sequenceNumber,先入隊的元素先出隊。
而 DelayedWorkQueue 也是 ScheduledExecutorService 能夠定時執(zhí)行任務的核心類。
首先回顧一下線程池的執(zhí)行流程:
當工作線程執(zhí)行 DelayedWorkQueue 的出隊方法時,DelayedWorkQueue 首先獲取到 time 值最小的 ScheduledFutureTask,即將要最先執(zhí)行的任務。然后用 time 值(任務要執(zhí)行的時間戳)與當前時間作比較,判斷任務執(zhí)行時間是否到期,若然到期,元素立馬出隊,交由工作線程執(zhí)行。
但是當 time 值還沒到期呢?那么 time 將會減去當前時間,得到 delay 值(延遲多少時間后執(zhí)行任務),然后使用方法Condition.awaitNanos(long nanosTimeout),阻塞獲取任務的工作線程,直到經過了 delay 時間,即到達了任務的執(zhí)行時間,元素才會出隊,交由工作線程執(zhí)行。
scheduleAtFixedRate 與 scheduleWithFixedDelay
根據我之前的理解,認為 scheduleAtFixedRate 是絕對周期性執(zhí)行,例如間隔周期為 10 秒,那么任務每隔 10 秒都會執(zhí)行一次,不管任務是否成功執(zhí)行。但是我的理解是錯誤的,這兩個方法的功能分別是:
要清楚,一個定時任務,不管是否為周期性執(zhí)行,都將會只由一條工作線程執(zhí)行
首先看下這兩個方法的源碼
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit) {if (command == null || unit == null)throw new NullPointerException();if (period <= 0)throw new IllegalArgumentException();ScheduledFutureTask<Void> sft =new ScheduledFutureTask<Void>(command,null,triggerTime(initialDelay, unit),unit.toNanos(period));RunnableScheduledFuture<Void> t = decorateTask(command, sft);sft.outerTask = t;delayedExecute(t);return t; }public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit) {if (command == null || unit == null)throw new NullPointerException();if (delay <= 0)throw new IllegalArgumentException();ScheduledFutureTask<Void> sft =new ScheduledFutureTask<Void>(command,null,triggerTime(initialDelay, unit),unit.toNanos(-delay));RunnableScheduledFuture<Void> t = decorateTask(command, sft);sft.outerTask = t;delayedExecute(t);return t; }其實兩個方法沒有太大區(qū)別,只是在構建 ScheduledFutureTask 的時候,ScheduledFutureTask 的 period 屬性有正負差別,scheduleAtFixedRate 方法構建 ScheduledFutureTask 的 period 為負數,而 scheduleWithFixedDelay 為正數。
接下來查看 ScheduledFutureTask 的 run 方法,工作線程在執(zhí)行任務時將會調用該方法
如果定時任務時周期性執(zhí)行方法,將會進入到 3 的執(zhí)行邏輯,當然在這之前將會調用 runAndReset 執(zhí)行任務邏輯。
當任務邏輯執(zhí)行完成后,將會調用 setNextRunTime。
如果 period 為正數數,即執(zhí)行的方法為 scheduleAtFixedRate,在任務的執(zhí)行時間上添加 period 時間。
而 period 為負數,即執(zhí)行的方法為 scheduleWithFixedDelay,將 time 改寫為當前時間加上 period 時間。
執(zhí)行完 setNextRunTime 方法后,將執(zhí)行 reExecutePeriodic 方法,即重新將該 ScheduledFutureTask 對象,重新添加到隊列中,等待下一次執(zhí)行。
要清楚,不論調用哪個周期性執(zhí)行方法,都是需要等到任務邏輯執(zhí)行完成后,才能再次添加到隊列中,等待下一次執(zhí)行。
scheduleAtFixedRate 方法,每次都是在 time 的基礎上添加 period 時間,如果任務邏輯的執(zhí)行時間大于 period,那么在定時任務再次出隊前,time 必定是小于當前時間,馬上出隊被工作線程執(zhí)行。因為 time 每次都是任務開始執(zhí)行的時間點。
scheduleWithFixedDelay 方法,每次都將 time 設置為當前時間加上 period,那么輪到定時任務再次出隊時,必定是經過了 period 時間,才能被工作線程執(zhí)行。
總結
對于 ScheduledExecutorService 一定要清楚,周期性執(zhí)行任務,一定是等到上一次執(zhí)行完成后,才能再次執(zhí)行,即每個任務只由一條線程執(zhí)行。那么要實現當達到一定時候后,不論任務是否執(zhí)行完成,都將再次執(zhí)行任務的功能,ScheduledExecutorService 的兩個周期性執(zhí)行方法都是不能實現的。其實也就是對于復雜的時間調度控制,ScheduledExecutorService 并不在行。
轉載于:https://my.oschina.net/bingzhong/blog/1559849
總結
以上是生活随笔為你收集整理的详解ScheduledExecutorService的周期性执行方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C++重载指针引用
- 下一篇: Linux CentOS6编译安装Pyt