目次
スレッドプールのステータス
ThreadPoolExecutor は、int の上位 3 ビットを使用してスレッド プールのステータスを示し、下位 29 ビットはスレッドの数を示します。
州名 |
上位3ビット |
新しいタスクを受け取る |
ブロッキングキュータスクを処理する |
説明する |
ランニング |
111 |
Y |
Y |
|
シャットダウン |
000 |
Y |
Y |
新しいタスクは受け入れられませんが、ブロックキュー内の残りのタスクは処理されます |
ストップ |
001 |
N |
N |
実行中のタスクを中断し、ブロックしているキューのタスクを破棄します。 |
片付け |
010 |
- |
- |
すべてのタスクが実行され、アクティブなスレッドが0になり、終了しようとしています。 |
終了しました |
011 |
- |
- |
端末状態 |
数値的には、TERMINATED > TIDYING > STOP > SHUTDOWN > RUNNING (最上位ビットは 1 で、負の数を意味します)。
これらの情報はアトミック変数 ctl に保存されます。その目的は、スレッド プールの状態とスレッドの数を 1 つに結合して、cas アトミック操作を割り当てに使用できるようにすることです。
// c 为旧值, ctlOf 返回结果为新值
ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))));
// rs 为高 3 位代表线程池状态, wc 为低 29 位代表线程个数,ctl 是合并它们
private static int ctlOf(int rs, int wc) { return rs | wc; }
施工方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize コアスレッドの数 (予約スレッドの最大数) minimumPoolSize スレッドの最大数
keepAliveTime 生存時間 - 緊急スレッド用
単位時間単位 - レスキュースレッド用
workQueue ブロックキュー
threadFactory スレッド ファクトリ - スレッドの作成時に適切な名前を付けることができます
ハンドラー拒否ポリシー
作業の方法:
5 つのタスクが来て、コア スレッドは 2、最大スレッドは 3、ブロッキング キュー サイズは 2
- スレッド プールには最初はスレッドがありませんが、タスクがスレッド プールに投入されると、スレッド プールはタスクを実行するための新しいスレッドを作成します。
- スレッド数が corePoolSize に達し、アイドル状態のスレッドがない場合は、この時点でタスクを追加します。アイドル状態のスレッドがなくなるまで、新しく追加されたタスクは workQueue キューに追加されます。
- キューがバウンドキューを選択している場合、タスクがキューサイズを超えた場合、緊急用にmaximumPoolSize - corePoolSize番号のスレッドが作成されます。
- スレッドがmaximumPoolSizeに達し、まだ新しいタスクがある場合、この時点で拒否ポリシーが実行されます。拒否戦略 jdk は 4 つの実装を提供しており、他の有名なフレームワークも実装を提供しています
AbortPolicy を使用すると、呼び出し元はデフォルトのポリシーである RejectedExecutionException をスローできます。
CallerRunsPolicy により、発信者がタスクを実行できるようになります
DiscardPolicy はこのタスクを放棄します
DiscardOldestPolicy は、キュー内の最も古いタスクを破棄し、このタスクに置き換えます。
Dubbo の実装は、RejectedExecutionException 例外をスローする前にログを記録し、問題の特定を容易にするためにスレッド スタック情報をダンプします。
Netty の実装では、タスクを実行するための新しいスレッドを作成します。
以前のカスタム拒否戦略と同様に、キューへの投入を試行する際にタイムアウト待機 (60 秒) を伴う ActiveMQ の実装
拒否ポリシーのチェーンを使用し、チェーン内の各拒否ポリシーを 1 つずつ試行する PinPoint の実装
- ピーク値が過ぎたとき、corePoolSize を超える緊急スレッドが一定期間実行するタスクがない場合、リソースを節約するためにスレッドを終了する必要があります。この時間は keepAliveTime と単位によって制御されます。
この構築方法に従って、JDK Executors クラスは、さまざまな目的でスレッド プールを作成するための多くのファクトリ メソッドを提供します。
新しい固定スレッドプール
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
特徴
- コア スレッドの数 == スレッドの最大数 (レスキュー スレッドは作成されない) なので、タイムアウトは必要ありません。
- ブロッキング キューには制限がなく、任意の数のタスクを保持できます。
評価はタスク量が既知のタスクや比較的時間のかかるタスクに適しています
新しいキャッシュされたスレッドプール
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
特徴
コアスレッドの数は 0、スレッドの最大数は Integer.MAX_VALUE、緊急スレッドのアイドルライフタイムは 60 秒です。つまり、
- すべて緊急スレッドです (60 秒後にリサイクル可能)
- 緊急スレッドは無限に作成可能
キューはSynchronousQueueを採用し、容量がなく、取得するスレッドが無いと入れられない特性を実現(片手決済、片手配送)
SynchronousQueue<Integer> integers = new SynchronousQueue<>();
new Thread(() -> {
try {
log.debug("putting {} ", 1);
integers.put(1);
log.debug("{} putted...", 1);
log.debug("putting...{} ", 2);
integers.put(2);
log.debug("{} putted...", 2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1").start();
sleep(1);
new Thread(() -> {
try {
log.debug("taking {}", 1);
integers.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t2").start();
sleep(1);
new Thread(() -> {
try {
log.debug("taking {}", 2);
integers.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t3").start();
}
11:48:15.500 c.TestSynchronousQueue [t1] - 1 を配置 11:48:16.500 c.TestSynchronousQueue [t2] - 1 を取得
11:48:16.500 c.TestSynchronousQueue [t1] - 1 パット...
11:48:16.500 c.TestSynchronousQueue [t1] - 2 を入れる
11:48:17.502 c.TestSynchronousQueue [t3] - 2 を取得
11:48:17.503 c.TestSynchronousQueue [t1] - 2 パット..
スレッドプール全体を評価すると、タスク量に応じてスレッド数は増加し続け、上限はなく、タスクが実行されると、1分間のアイドル時間が経過するとスレッドが解放されます。タスクの数は比較的集中しているが、各タスクの実行時間が短い状況に適しています。
newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
使用シナリオ: 複数のタスクが実行のためにキューに入れられることが望まれます。スレッド数は1固定で、タスク数が1を超える場合は無制限キューにキューイングされます。タスクの実行後、唯一のスレッドは解放されません。
違い:
- シングルスレッドのシリアル実行タスクを自分で作成します。タスクの実行が失敗して終了した場合、対処法はなく、スレッド プールはプールの正常な動作を保証するために新しいスレッドも作成します。
- Executors.newSingleThreadExecutor() スレッドの数は常に 1 であり、変更できません
- FinalizableDelegatedExecutorService はデコレータ モードを適用し、ExecutorService インターフェイスのみを公開するため、ThreadPoolExecutor 内の固有のメソッドを呼び出すことはできません。
- Executors.newFixedThreadPool(1) は最初は 1 であり、将来変更できます。公開された ThreadPoolExecutor オブジェクトは、変換後に setCorePoolSize およびその他のメソッドを呼び出すことで変更できます。
タスクを送信する
// 执行任务
void execute(Runnable command);
// 提交任务 task,用返回值 Future 获得任务执行结果
<T> Future<T> submit(Callable<T> task);
// 提交 tasks 中所有任务
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
// 提交 tasks 中所有任务,带超时时间
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException;
// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException;
// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消,带超时时间
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
スレッドプールを閉じる
シャットダウン
/*
线程池状态变为 SHUTDOWN
- 不会接收新任务
- 但已提交任务会执行完
- 此方法不会阻塞调用线程的执行
*/
void shutdown();
今すぐシャットダウン
/*
线程池状态变为 STOP
- 不会接收新任务
- 会将队列中的任务返回
- 并用 interrupt 的方式中断正在执行的任务
*/
List<Runnable> shutdownNow();
他の方法
// 不在 RUNNING 状态的线程池,此方法就返回 true
boolean isShutdown();
// 线程池状态是否是 TERMINATED
boolean isTerminated();
// 调用 shutdown 后,由于调用线程并不会等待所有任务运行结束,
//因此如果它想在线程池 TERMINATED 后做些事情,可以利用此方法等待
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;