6、スレッドプール
1.利点を使用する
-
リソース消費を削減します
作成したスレッドを再利用して、スレッドの作成と破棄による消費を減らすことができます
-
応答速度を向上させる
タスクが到着すると、スレッドが作成されるのを待たずにすぐに実行できます
-
スレッドの管理性を向上させる
スレッドは希少なリソースであり、無限に作成するとシステムリソースが消費されるだけでなく、システムの安定性も低下します。スレッドプールの使用は、均一な割り当て、調整、および監視に使用できます
2.スレッドプールの作成
java.util.concurrent.ThreadPoolExecutor
作成することにより
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
}
corePoolSize:コアスレッドの数
maximumPoolSize:スレッドの最大数(コアスレッドの数を含む)
keepAliveTime:非コアスレッドの存続時間(数値)。非コアスレッド(アイドル状態)が新しいタスクの終了を待機する最大時間(終了する前に新しいタスクを待機する)java.lang.Thread.State: WAITING (parking)
ユニット:非コアスレッドの存続時間(ユニット)
workQueue:スレッドタスクキュー
threadFactory:スレッドを生成するためのメソッドファクトリ。たとえば、デフォルトの静的メソッドを使用して作成されたスレッドファクトリjava.util.concurrent.Executors#defaultThreadFactory
ハンドラー:スレッドタスク拒否戦略。たとえば、デフォルトの拒否実行戦略を使用しますjava.util.concurrent.ThreadPoolExecutor.AbortPolicy
/* 会直接抛出异常 */
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
3.作業方法
- スレッドタスクを実行するときは、最初にコアスレッドの数(corePoolSize)に達しているかどうかを判断します。達していない場合は、スレッドタスクを実行するための新しいスレッドが作成されます。到達していない場合、スレッドタスクはブロッキングキュー(BlockingQueue)に追加されます。
- ブロッキングキューがいっぱいで、スレッドの最大数(maximumPoolSize)に達していない場合、スレッドタスクを実行するための新しいスレッドが作成されます
- スレッドの最大数に達したときに、スレッドタスクを実行する必要がある場合、拒否戦略(ハンドラー)が実行されます
- 追加のスレッド(非コアスレッド)がアイドル状態で最大待機時間に達すると、それらは終了します(TERMINATED)
注:新しいスレッドを作成するときは、グローバルロックを取得する必要があります
/**
* Lock held on access to workers set and related bookkeeping.
* While we could use a concurrent set of some sort, it turns out
* to be generally preferable to use a lock. Among the reasons is
* that this serializes interruptIdleWorkers, which avoids
* unnecessary interrupt storms, especially during shutdown.
* Otherwise exiting threads would concurrently interrupt those
* that have not yet interrupted. It also simplifies some of the
* associated statistics bookkeeping of largestPoolSize etc. We
* also hold mainLock on shutdown and shutdownNow, for the sake of
* ensuring workers set is stable while separately checking
* permission to interrupt and actually interrupting.
*/
private final ReentrantLock mainLock = new ReentrantLock();
private boolean addWorker(Runnable firstTask, boolean core) {
// ... ...
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
並行性を使用することもできますが、ロックを使用することをお勧めします。スレッドの安全性を確保してスレッドプールを安定して実行できる一方で、一部の操作が簡素化されるため(スレッドプールのソースコードの実装)
4.ベストプラクティス
- スレッドの最大数を合理的に構成する
- CPUを集中的に使用するタスク:CPUコアの数+1 [スレッドの切り替えを減らす]
- IOを多用するタスク[計算方法1]:CPUコア数×2
- IOを多用するタスク[計算方法2]:CPUコア数/(1-ブロッキング係数)、ブロッキング係数は0.8〜0.9 [スループットの向上]
- 非コアスレッドのkeepAliveTimeを適切に構成します。実行するタスクが多く、タスクの実行時間が短い場合は、この時間を増やしてスレッドの使用率を向上させることができます。
- ブロッキングキューのサイズを適切に構成します。サイズが大きすぎると、タスクを実行できないだけでなく、メモリリソースが使い果たされる可能性があります。
shutdown
スレッドプールを閉じるには2つの方法があります。スレッドプールを閉じるだけですが、未完了のタスクは引き続き実行されます。shutdownNow
スレッドプールを閉じることですが、未完了のタスクを終了する必要があります。