java time 周期性执行,详解ScheduledExecutorService的周期性执行方法
詳解 ScheduledExecutorService 的周期性執行方法
在最近的工作中,需要實現一個當一個任務執行完后,再等 100 毫秒然后再次執行的功能。當時最先反映到的就是 java 線程池的 ScheduledExecutorService,而 ScheduledExecutorService 有兩個周期性執行任務的方法,分別是 scheduleAtFixedRate 與 scheduleWithFixedDelay,當時對這兩個方法也不大了解,感覺和我的理解有所偏差,所以對這兩個方法進行了研究。
ScheduledExecutorService 的基本原理
想要了解 scheduleWithFixedDelay 和 scheduleAtFixedRate 這兩個周期性執行任務的方法,首先要了解 ScheduledExecutorService 的原理。在《java 并發編程的藝術》一書中有詳細的解說,這里就簡單的闡述一下。
ScheduledExecutorService 與其他線程池的區別,主要在于在執行前將任務封裝為ScheduledFutureTask與其使用的阻塞隊列DelayedWorkQueue。
ScheduledFutureTask
private class ScheduledFutureTask
extends FutureTask implements RunnableScheduledFuture {
/** 表示這個任務添加到ScheduledExecutorService中的序號 */
private final long sequenceNumber;
/** T表示這個任務將要被執行的具體時間(時間戳) */
private long time;
/**
* 表示任務執行的間隔周期,若為0則表示不是周期性執行任務
*/
private final long period;
/*省略以下代碼*/
}
DelayedWorkQueue
DelayedWorkQueue 是一個優先隊列,在元素出隊時,ScheduledFutureTask 的 time 最小的元素將優先出隊,如果 time 值相同則判斷 sequenceNumber,先入隊的元素先出隊。
而 DelayedWorkQueue 也是 ScheduledExecutorService 能夠定時執行任務的核心類。
首先回顧一下線程池的執行流程:
向線程池提交任務,這時任務將入隊到該線程池的阻塞隊列
工作線程不斷從隊列中取出任務,并執行,若然隊列中沒有任務,工作線程將阻塞直到任務的到來。
當工作線程執行 DelayedWorkQueue 的出隊方法時,DelayedWorkQueue 首先獲取到 time 值最小的 ScheduledFutureTask,即將要最先執行的任務。然后用 time 值(任務要執行的時間戳)與當前時間作比較,判斷任務執行時間是否到期,若然到期,元素立馬出隊,交由工作線程執行。
但是當 time 值還沒到期呢?那么 time 將會減去當前時間,得到 delay 值(延遲多少時間后執行任務),然后使用方法Condition.awaitNanos(long nanosTimeout),阻塞獲取任務的工作線程,直到經過了 delay 時間,即到達了任務的執行時間,元素才會出隊,交由工作線程執行。
scheduleAtFixedRate 與 scheduleWithFixedDelay
根據我之前的理解,認為 scheduleAtFixedRate 是絕對周期性執行,例如間隔周期為 10 秒,那么任務每隔 10 秒都會執行一次,不管任務是否成功執行。但是我的理解是錯誤的,這兩個方法的功能分別是:
scheduleAtFixedRate:任務執行完成后,在提交任務到任務執行完成后的時間是否經過了 period,若然經過了,即馬上再次執行該任務。否則等待,直到提交任務到現在已經經過了 period 時間,再次執行該任務。
scheduleWithFixedDelay:任務執行完成后,等待 delay 時間,然后再次執行。
要清楚,一個定時任務,不管是否為周期性執行,都將會只由一條工作線程執行
首先看下這兩個方法的源碼
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 sft =
new ScheduledFutureTask(command,
null,
triggerTime(initialDelay, unit),
unit.toNanos(period));
RunnableScheduledFuture 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 sft =
new ScheduledFutureTask(command,
null,
triggerTime(initialDelay, unit),
unit.toNanos(-delay));
RunnableScheduledFuture t = decorateTask(command, sft);
sft.outerTask = t;
delayedExecute(t);
return t;
}
其實兩個方法沒有太大區別,只是在構建 ScheduledFutureTask 的時候,ScheduledFutureTask 的 period 屬性有正負差別,scheduleAtFixedRate 方法構建 ScheduledFutureTask 的 period 為負數,而 scheduleWithFixedDelay 為正數。
接下來查看 ScheduledFutureTask 的 run 方法,工作線程在執行任務時將會調用該方法
/**
* Overrides FutureTask version so as to reset/requeue if periodic.
*/
public void run() {
boolean periodic = isPeriodic();
if (!canRunInCurrentRunState(periodic))
cancel(false);//1
else if (!periodic)
ScheduledFutureTask.super.run();//2
else if (ScheduledFutureTask.super.runAndReset()) {
//3
setNextRunTime();
reExecutePeriodic(outerTask);
}
}
如果定時任務時周期性執行方法,將會進入到 3 的執行邏輯,當然在這之前將會調用 runAndReset 執行任務邏輯。
當任務邏輯執行完成后,將會調用 setNextRunTime。
/**
* Sets the next time to run for a periodic task.
*/
private void setNextRunTime() {
long p = period;
if (p > 0)
time += p;
else
time = triggerTime(-p);
}
long triggerTime(long delay) {
return now() +
((delay < (Long.MAX_VALUE >> 1)) ? delay : overflowFree(delay));
}
如果 period 為正數數,即執行的方法為 scheduleAtFixedRate,在任務的執行時間上添加 period 時間。
而 period 為負數,即執行的方法為 scheduleWithFixedDelay,將 time 改寫為當前時間加上 period 時間。
執行完 setNextRunTime 方法后,將執行 reExecutePeriodic 方法,即重新將該 ScheduledFutureTask 對象,重新添加到隊列中,等待下一次執行。
要清楚,不論調用哪個周期性執行方法,都是需要等到任務邏輯執行完成后,才能再次添加到隊列中,等待下一次執行。
scheduleAtFixedRate 方法,每次都是在 time 的基礎上添加 period 時間,如果任務邏輯的執行時間大于 period,那么在定時任務再次出隊前,time 必定是小于當前時間,馬上出隊被工作線程執行。因為 time 每次都是任務開始執行的時間點。
scheduleWithFixedDelay 方法,每次都將 time 設置為當前時間加上 period,那么輪到定時任務再次出隊時,必定是經過了 period 時間,才能被工作線程執行。
總結
對于 ScheduledExecutorService 一定要清楚,周期性執行任務,一定是等到上一次執行完成后,才能再次執行,即每個任務只由一條線程執行。那么要實現當達到一定時候后,不論任務是否執行完成,都將再次執行任務的功能,ScheduledExecutorService 的兩個周期性執行方法都是不能實現的。其實也就是對于復雜的時間調度控制,ScheduledExecutorService 并不在行。
總結
以上是生活随笔為你收集整理的java time 周期性执行,详解ScheduledExecutorService的周期性执行方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php的数据校验,php 数据类型校验函
- 下一篇: comsol计算数据导出matlab,c