ScheduledThreadPoolExecutorには、実行間隔を指定して、指定したタスクを定期的に実行できる機能があります。これは、タイマーや時間指定タスクの機能に似ています。ScheduledThreadPoolExecutorのパフォーマンスはタイマーよりも高いとオンラインで言われています。 。私はまだこれを研究していないので、比較はしていません。このメモは主にソースコード学習の一部を記録しています。
クラス構造
ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutorはスレッドプールThreadPoolExecutorを継承していることがわかります。さらに、ScheduledExecutorServiceも実装されており、このクラスが少なくともスレッドプールであることを示しています。さらに、スレッドプールに加えて、他の関数がいくつかあります。
ScheduledExecutorService
ScheduledExecutorServiceは、4つのメソッドを定義するインターフェイスであることがわかります。
public interface ScheduledExecutorService extends ExecutorService {
public ScheduledFuture<?> schedule(Runnable command,
long delay, TimeUnit unit);
public <V> ScheduledFuture<V> schedule(Callable<V> callable,
long delay, TimeUnit unit);
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit);
}
これら4つの方法の最初の2つ、つまり1回だけ実行される遅延タスクを開始する方法については説明していません。
次の2つは、遅延がタスクを定期的に実行できるようにするためのものであり、以下で詳しく説明します。
DelayedWorkQueue
このクラスの役割は、次のように理解できると思います。DelayedWorkQueue= DelayQueue + PriorityQueueこれらの3つのクラスに関するメモ、記録する別のメモを書きたいので、ここで
はあまり説明しません。これは内部と見なすことができます。クラス完了した操作は次のとおりです。時限タスクの要素がキューに挿入されると、優先度が並べ替えられ、最初に実行されたタスクが最前面に配置されます。
例:タスクは5Sの後に実行され、Bこの時点でタスクが挿入されます。Bタスクは2S後に実行する必要があり、次にAの前にBを挿入します。
ScheduledFutureTask
このクラスの役割、私の現在の理解は次のとおりです。実行するように指定したタスクのパッケージ化のレイヤー
ソースコード
パブリックメソッド1、triggerTime(delay、unit)
この方法は、タスクの実行時間として現在の時間に基づいて遅延時間を追加することです。
long triggerTime(long delay) {
return now() +
((delay < (Long.MAX_VALUE >> 1)) ? delay : overflowFree(delay));
}
パブリックメソッド2、decorateTask
このメソッドは主に、指定されたタスクを再パッケージ化し、ScheduledFutureTaskオブジェクトにパッケージ化するためのものです。
パブリックメソッド3、delayedExecute
この方法は、パッケージ化されたタスクをタスクキューに追加することです
/**
* 这个方法是把任务添加到队列中,在scheduledThreadPoolExecutor中,这里的队列是优先级队列
* 1.添加到任务队列之前,会判断下线程池的状态,如果是非运行状态,就执行拒绝策略
* 2.在添加到任务队列之后,如果线程池是shutdown状态,就remove,并且将任务取消
* 3.如果不需要取消任务,就执行ensurePrestart(); 在该方法中,会判添加一个空的worker任务,去执行队列中的任务
* @param task the task
*/
private void delayedExecute(RunnableScheduledFuture<?> task) {
if (isShutdown())
reject(task);
else {
super.getQueue().add(task);
if (isShutdown() &&
!canRunInCurrentRunState(task.isPeriodic()) &&
remove(task))
task.cancel(false);
else
ensurePrestart();
}
}
/**
* Same as prestartCoreThread except arranges that at least one
* thread is started even if corePoolSize is 0.
* 判断当前线程池中工作线程的数量
* 如果小于核心线程数,就添加一个核心线程
* 如果大于核心线程数,就添加一个非核心线程
* 这里是空任务的原因:就是为了开启一个线程,去执行任务队列中排队的任务
*/
void ensurePrestart() {
int wc = workerCountOf(ctl.get());
if (wc < corePoolSize)
addWorker(null, true);
else if (wc == 0)
addWorker(null, false);
}
surePrestart:このメソッドで、ワーカーオブジェクトを追加します。スレッドプールのソースコードに記録しました。実際、スレッドプールのスレッドはワーカーオブジェクトにパッケージ化され、スレッドプールに格納されます。ここにnullを追加します。スレッドを作成するタスクキュー内のタスクを実行するには
チームに参加する方法
super.getQueue()。add(task);を呼び出すと、対応するオファーメソッドが呼び出されてキューに入れられます
/**
* 入队方法,将待执行的任务插入到队列中
* 在入队的时候,会进行优先级的判断
* @param x
* @return
*/
public boolean offer(Runnable x) {
if (x == null)
throw new NullPointerException();
RunnableScheduledFuture<?> e = (RunnableScheduledFuture<?>)x;
final ReentrantLock lock = this.lock;
/**
* 1.加锁
*/
lock.lock();
try {
int i = size;
/**
* 2.判断是否需要进行扩容
* 这里的扩容,和ArrayList扩容的方法类型:
* 先扩容50%,然后通过Arrays.copy将数组扩容之后的数据,再复制到queue中
*/
if (i >= queue.length)
grow();
size = i + 1;
/**
* 3.如果当前插入的是第一个任务
* 就将e设置为头结点
*
* 否则的话,就进行优先级的处理
*/
if (i == 0) {
queue[0] = e;
setIndex(e, 0);
} else {
siftUp(i, e);
}
/**
* 4.这里是如果插入了第一个元素,去通知
* take方法,这里的available是一个condition对象
*/
if (queue[0] == e) {
leader = null;
available.signal();
}
} finally {
lock.unlock();
}
return true;
}
上記のキューに入る方法は、コメントが悪くないのであまり説明しません。キューに入るときは、キュー内の要素をキューに入れるという重要な方法があります。要素が挿入されます。、現在の要素を挿入する場所を決定するには、要素に対応する有効期限、つまり実行時間に従って比較します。実行が早いほど、前に配置します。
優先順位付け
/**
* Sifts element added at bottom up to its heap-ordered spot.
* Call only when holding lock.
* 这是DelayedWorkQueue自己实现的,在入队时,进行优先级判断的逻辑
* k:当前待插入元素要入队的位置
* key:就是要入队的任务
*/
private void siftUp(int k, RunnableScheduledFuture<?> key) {
while (k > 0) {
/**
* 1.获取到k对应的父节点元素
*/
int parent = (k - 1) >>> 1;
RunnableScheduledFuture<?> e = queue[parent];
/**
* 2.如果任务k执行的时间晚于e父节点的,就无需再遍历处理
* 如果k的执行时间早于e,那就需要交换位置,然后再次遍历判断父节点和交换之后的优先级
*/
if (key.compareTo(e) >= 0)
break;
queue[k] = e;
setIndex(e, k);
k = parent;
}
/**
* 设置待插入元素的实际位置
*/
queue[k] = key;
setIndex(key, k);
}
比較では、現在の要素の親要素と比較されます。これは、バイナリツリーが格納に使用されるため、判断のコアメソッドはcompareToメソッドであり、RunnableScheduledFutureもこのメソッドを上書きします。
/**
* 用来比较优先级,这里的other是插入元素要对比的元素
* @param other
* @return
*/
public int compareTo(Delayed other) {
if (other == this) // compare zero if same object
return 0;
if (other instanceof ScheduledFutureTask) {
ScheduledFutureTask<?> x = (ScheduledFutureTask<?>)other;
/**
* 如果当前要插入的元素对应的时间 早于X节点执行,那就返回-1
* 如果要插入的元素对应的执行时间 晚于X节点执行,那就返回1
* 举例:X要在5S之后执行,但是当前插入的元素在2S之后执行,那这里的diff就小于0,返回-1
* 如果X要5S之后执行,但是待插入元素是10S之后执行,那这里的diff就大于0,返回1
* 至于下面的sequenceNumber应该是在任务是同时执行的情况下,再进行的优先级判断吧
*/
long diff = time - x.time;
if (diff < 0)
return -1;
else if (diff > 0)
return 1;
else if (sequenceNumber < x.sequenceNumber)
return -1;
else
return 1;
}
/**
* 如果要比较的任务不是ScheduledFutureTask,那就直接获取到每个任务还有多少毫秒要执行,进行优先级判断
*/
long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS);
return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
}
キュー内のタスクを実行する
チームに入ると、ドアで指定されたタスクがScheduledFutureTaskにパッケージ化されるため、実行されると、対応するrunメソッドが実行されます。
/**
* Overrides FutureTask version so as to reset/requeue if periodic.
* 自定义的任务在执行的时候,实际调用的就是这个方法,因为线程池对任务进行了一层包装
*/
public void run() {
/**
* 1.首先判断是否需要重复执行,这个值是在初始化的时候,指定的
* 如果只需要执行一次,这里返回的就是false
* 如果需要周期定时执行,这里返回的就是true
* 根据period的值来判断
*/
boolean periodic = isPeriodic();
/**
* 2.这里没看懂,判断是否需要取消任务?
*/
if (!canRunInCurrentRunState(periodic))
cancel(false);
/**
* 3.如果只需要执行一次,就会执行这里的逻辑
*/
else if (!periodic)
ScheduledFutureTask.super.run();
/**
* 4.如果是需要重复执行的,就执行这里的方法
* 如果任务正常执行成功,就继续设置下次的执行时间
* setNextRunTime():是在当前时间的基础之上,加上第一次指定的延迟时间
* reExecutePeriodic():是将任务再次加入队列中
*/
else if (ScheduledFutureTask.super.runAndReset()) {
setNextRunTime();
reExecutePeriodic(outerTask);
}
}
4点目は繰り返し実行の要点です。繰り返し実行が必要と判断された場合は
1に再入力し、ScheduledFutureTask構築方法により繰り返し実行可能かどうかを判断します。scheduleAtFixedRateとコンストラクターの違い
2.繰り返し実行する必要がある場合、タスクは再度キューに入れられます。
/**
* Requeues a periodic task unless current run state precludes it.
* Same idea as delayedExecute except drops task rather than rejecting.
*
* 将需要重复执行的任务,再次入队
* @param task the task
*/
void reExecutePeriodic(RunnableScheduledFuture<?> task) {
if (canRunInCurrentRunState(true)) {
super.getQueue().add(task);
if (!canRunInCurrentRunState(true) && remove(task))
task.cancel(false);
else
ensurePrestart();
}
}
工法
public ScheduledThreadPo olExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
構築方法は、そのうちの1つを選択しました。構築方法は、スレッドプールを初期化することでもあります。特別な場所は、このスレッドプールクラスが指定されたキューをサポートしないことです。DelayedWorkQueueのみを使用できます。このキューの使用を指定する実装。優先順位付け
スケジュール
/**
* @throws RejectedExecutionException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
* 这是只执行一次的方法,也就是说:callable会在delay秒之后执行
* 执行一次之后,就结束
*/
public <V> ScheduledFuture<V> schedule(Callable<V> callable,
long delay,
TimeUnit unit) {
if (callable == null || unit == null)
throw new NullPointerException();
RunnableScheduledFuture<V> t = decorateTask(callable,
new ScheduledFutureTask<V>(callable,
triggerTime(delay, unit)));
delayedExecute(t);
return t;
}
この方法は単純明快です。現在の時刻と指定された遅延イベント遅延に基づいて指定されたタスクをキューに入れると、スレッドプールのスレッドがタスクキューに移動して実行されます。
時限サイクル実行
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;
}
/**
* @throws RejectedExecutionException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
* @throws IllegalArgumentException {@inheritDoc}
*/
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;
}
これら2つのメソッドを一緒に見ると、違いは1つだけです。つまり、タスクがScheduledFutureTaskオブジェクトにパッケージ化されている場合、1つは負の数で、もう1つは正の数です。ここでの違いはまだ調査されていません。後でそれについて話します。
ここでの全体的なロジックは次のとおり
です。1。タスクをScheduledFutureTaskにラップします
。2。タスクを一時変数に割り当てます。この一時変数outerTaskは非常に重要であり、再度キューに入れるためのキー変数です。3
。次にタスクがキューに入れられます。
総括する:
- ScheduledThreadPoolExecutorは、定期的なタイミングタスクをサポートします。jdk6以前はDelayQueueに依存していましたが、jdk6以降、ScheduledThreadPoolExecutorはDelayQueueと同様のロジックであるDelayedWorkQueueを実装しています。
- チームに入るとき、優先度が判断されます。いわゆる優先度は、タスクの実行順序に従って並べ替えられます。前に配置されたものは、常に最近実行されたものです。
- チームを離れた後、タスクを繰り返し実行する必要があるのか、1回だけ実行する必要があるのかが判断されます。
- 一度だけ実行する必要がある場合は、実行後に終了します
- 複数回実行する必要がある場合は、実行が完了した後、タスクが再度キューに入れられます。同じタスク、同じ遅延時間、エンキューは別の優先キューイングを実行します。