Javaスレッドプールの迅速な理解
11461 ワード
私のブログ
1.概要
Threadクラスを使用してタスクを実行すると、タスクの実行時に毎回スレッドが作成され、タスクが終了するとスレッドが破棄されます.システムにとって、スレッドはリソースだけでなく、スレッドの作成と破棄もシステムのリソースを消費します.この問題に対して、直接的な解決策は、スレッドを多重化し、スレッドがタスクを実行した後も破棄ではなく他のタスクを実行し続けることができるようにすることであることは明らかです.スレッドプールでは、このようなソリューションが提供されます.
スレッドプールとは、作成後は一般的に破棄されず、新しいタスクが実行されるまで休止状態に入り、実行を再開するスレッドのセットです.
2.スレッドプールの作成
java.util.concurrent(JUC)パッケージには、スレッドプールの作成に多くのインタフェースが用意されています.
2.1 ExecutorServiceインタフェース
スレッドプールは、通常、管理を終了する方法と、Futureを生成する方法を提供する
2.2 ThreadPoolExecutorによるスレッドプールの直接作成
JUCは、ExecutorServiceインタフェースを実装するAbstractThreadPoolExecutorを継承するスレッドプールを作成するためのThreadPoolExecutorクラスを提供します.ThreadPoolExecutorにはいくつかの構造パラメータがあります.一般的には、次のようにスレッドプールを作成するために呼び出すことができます.
コードは次のとおりです.
各パラメータの意味は次のとおりです. が呼び出されない限り、スレッドがアイドルであっても生存するスレッドの数を維持します. のみが実行されます.
上記のパラメータの説明から、ThreadPoolExecutorによって作成されたスレッドプールは自動的にサイズを調整できることがわかります.新しいタスクがコミットされると、スレッドプール内のスレッドが
2.3スレッドプールにおけるスレッドの作成
スレッドプールはThreadFactoryを使用して新しいスレッドを作成します.特に指定されていない場合は、
2.4スレッド生存時間
プールが現在
2.5ワークキュー
任意のBlockingQueueは、コミットされたタスクを転送および保存するために使用できます.このキューの使用は、プール・サイズと相互に影響します.実行中のスレッドがcorePoolSizeより少ない場合、Executorは常にタスクをキューに追加するのではなく、新しいスレッドを追加するのが好きです. corePoolSizeまたはそれ以上のスレッドが実行されている場合、Executorは常に新しいスレッドを追加するのではなく、キューリクエストを好む. キューがいっぱいになった場合、maximumPoolSizeを超えない限り、新しいスレッドが作成されます.この場合、タスクは拒否されます.
キューの選択には、次の3つの一般的なポリシーがあります.直接引き継ぎます.ワークキューのデフォルトの選択は 無限キュー.無制限キューを使用すると(たとえば、事前に定義された容量を持たないLinkedBlockingQueue)、新しいタスクは空きスレッドがない場合にキュー内で待機します.したがって、スレッドプールにはcorePoolSize数のスレッドしか作成されません.( の限られたキュー.有限キュー(ArrayBlockingQueueなど)は、有限maximumPoolSizesと一緒に使用すると、リソースの消費を防ぐのに役立ちますが、調整や制御が困難になる場合があります.キューサイズと最大プールサイズの間では、互いに折衷できます.(trade off):大規模なキューと小型プールを使用すると、CPUの使用率、OSリソース、コンテキスト切替のオーバーヘッドを最小限に抑えることができますが、人為的な低スループットを引き起こす可能性があります.タスクが常にブロックされている場合(例えば、実行時にI/O操作がある場合)、システムは 2.6タスクの発行
タスクをコミットするには、次の2つの方法があります.1つは、戻り値のないタスクをコミットする方法です.
タスクの実行結果を取得する必要がある場合は、submitメソッドを呼び出してタスクをコミットできます.コードは次のとおりです.
2.7スレッドプールの完全なインスタンスの作成
3 Executorのファクトリメソッドによるスレッドプールの作成
ExecutorsはJUCパッケージの下にあるファクトリクラスで、スレッドプールの作成に便利な方法を提供しています.
3.1固定サイズスレッドプールの作成
Executorsクラスを使用して、固定サイズのスレッドプールを作成できます.
固定サイズのスレッドプールは、ThreadPoolExecutorコアスレッド数と最大スレッド数を等しく設定することで実現できますが、実際にはソースコードもそうです.
一般に、スレッド数がコアスレッド数に達すると、スレッドの作成もスレッドの破棄も行われません.異常なスレッド終了が発生したり、allowCoreTimeOutを設定したりしない限り、keepAliveパラメータが0に設定されているため、スレッドの作成と破棄が頻繁になる可能性があります(ここでは推測のみです.検証が必要です).
3.2キャッシュ可能なスレッドプールの作成:
前述したように、
3.3単一スレッドのスレッドプールの作成
単一スレッドのExecutorを
4まとめ
この記事では、スレッドプールの作成方法、各パラメータの意味と役割について詳しく説明しますが、スレッドプールには、上記のほかにスケジューリング可能なスレッドプールがあります.このシリーズの次の記事では、このスレッドプールについて説明します.
1.概要
Threadクラスを使用してタスクを実行すると、タスクの実行時に毎回スレッドが作成され、タスクが終了するとスレッドが破棄されます.システムにとって、スレッドはリソースだけでなく、スレッドの作成と破棄もシステムのリソースを消費します.この問題に対して、直接的な解決策は、スレッドを多重化し、スレッドがタスクを実行した後も破棄ではなく他のタスクを実行し続けることができるようにすることであることは明らかです.スレッドプールでは、このようなソリューションが提供されます.
スレッドプールとは、作成後は一般的に破棄されず、新しいタスクが実行されるまで休止状態に入り、実行を再開するスレッドのセットです.
2.スレッドプールの作成
java.util.concurrent(JUC)パッケージには、スレッドプールの作成に多くのインタフェースが用意されています.
2.1 ExecutorServiceインタフェース
スレッドプールは、通常、管理を終了する方法と、Futureを生成する方法を提供する
ExecutorService
を指す.簡単に言えば、Executor
インターフェースはExecutorService
インターフェースを継承し、Executor
はExecutorService
によって提供されるexecuteメソッドに加えて、管理を終了する多くの方法と、スレッドの実行結果を追跡するためのFutureを生成するsubmitを提供する.以下に示す
boolean awaitTermination(long timeout, TimeUnit unit)
List> invokeAll(Collection extends Callable> tasks)
List> invokeAll(Collection extends Callable> tasks, long timeout, TimeUnit unit)
T invokeAny(Collection extends Callable> tasks)
T invokeAny(Collection extends Callable> tasks, long timeout, TimeUnit unit)
boolean isShutdown()
boolean isTerminated()
void shutdown()
List shutdownNow()
Future submit(Callable task)
Future> submit(Runnable task)
Future submit(Runnable task, T result)
2.2 ThreadPoolExecutorによるスレッドプールの直接作成
JUCは、ExecutorServiceインタフェースを実装するAbstractThreadPoolExecutorを継承するスレッドプールを作成するためのThreadPoolExecutorクラスを提供します.ThreadPoolExecutorにはいくつかの構造パラメータがあります.一般的には、次のようにスレッドプールを作成するために呼び出すことができます.
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue)
コードは次のとおりです.
ExecutorService executor = new TreadPoolExecutor(5, 10, 10L,TimeUnit.SECONDS,new BlockingArrayQueue(40));
各パラメータの意味は次のとおりです.
Executor
スレッドプールでは、allowCoreThreadTimeOut(true)corePoolSize
スレッドプールで作成できる最大スレッド数corePoolSize
アイドルスレッドの生存時間keepAliveTime
KeepAliveTimeパラメータの単位、例えばTimeUnit.SECONDES等unit
コミットされたrunnableのタスクを格納するキューで、executeメソッドでコミットされたrunnableタスク上記のパラメータの説明から、ThreadPoolExecutorによって作成されたスレッドプールは自動的にサイズを調整できることがわかります.新しいタスクがコミットされると、スレッドプール内のスレッドが
workQueue
未満の場合、他のスレッドがアイドル状態であっても、スレッドプールは新しいスレッドを作成してタスクを実行します.このとき、スレッドプールにcorePoolSize
より大きいがcorePoolSize
より小さいスレッド数が存在する場合、maximumPoolSize
が満タンでない限り、新しいスレッドは作成されません.したがって、workQueue
とcorePoolSize
を等しく設定することによって、固定スレッド数のスレッドプールを作成することもできるし、maximumPoolSize
のような実質的に境界のない値を設定することによって、任意の同時スレッド数を収容できるスレッドプールを作成することもできる.一般的には、Integer.MAX_VALUE
およびcorePoolSize
のスレッドプール構造が決定されていますが、maximumPoolSize
およびsetCorePoolSize(int)
によってこの2つの値を動的に設定することもできます.2.3スレッドプールにおけるスレッドの作成
スレッドプールはThreadFactoryを使用して新しいスレッドを作成します.特に指定されていない場合は、
setMaximumPoolSize(int)
を使用して、すべてのスレッドを同じThreadGroupに作成し、同じExecutors.defaultThreadFactory()
優先度と非デーモン・プロセス・ステータスを有します.異なるThreadFactoryを提供することで、スレッドの名前、スレッドグループ、優先度、デーモンステータスなどを変更できます.NORM_PRIORITY
がnewThreadを呼び出したときにThreadFactory
に戻ってスレッドの作成に失敗した場合、executorは続行しますが、タスクを実行できない場合があります.スレッドには「modifyThread」のRuntimePermissionがあるはずです.スレッド・プールを使用しているワーク・スレッドまたは他のスレッドにこの権限がない場合、サービスはダウングレードされます.構成の変更がタイムリーに有効にならない可能性があります.また、スレッド・プールを閉じると、終了できますが完了していない状態が維持されます.2.4スレッド生存時間
プールが現在
null
を超えるスレッドを有する場合、余分なスレッドがcorePoolSize
を超える場合、終了します(keepAliveTime
を参照).これにより、プールを使用しないときにリソース消費量を削減する方法が提供されます.スレッドプールが後でアクティブになると、新しいスレッドが構築されます.方法setKeepAliveTimeも使用できます.(long,TimeUnit)このパラメータを動的に変更します.getKeepAliveTime(TimeUnit)
、Long.MAX_VALUE
のような値を使用すると、アイドルスレッドのオンライン・スレッド・プールが閉じる前に終了することが有効になります.デフォルトでは、corePoolSizeより大きいスレッドが存在する場合にのみ、アクティブなポリシーを維持できます.ただし、keepAliveTime値がゼロ.2.5ワークキュー
任意のBlockingQueueは、コミットされたタスクを転送および保存するために使用できます.このキューの使用は、プール・サイズと相互に影響します.
キューの選択には、次の3つの一般的なポリシーがあります.
TimeUnit.NANOSECONDS
であり、タスクをスレッドに渡し、別途保存しない.タスクを実行するためにスレッドがすぐに使用できない場合は、新しいタスクを追加しようとすると失敗し、新しいスレッドが構築されます.このポリシーは、内部依存性のあるリクエストセットを処理するときにロックを回避します.直接引継ぎ法は、通常、新しいコミットされたタスクを拒否することを避けるために、境界のないallowCoreThreadTimeOut(boolean)
を必要とする.これは、タスクが処理よりも速い速度で到達すると、スレッドが無制限に増加する可能性があることを意味します.SynchronousQueue
の値なので効果はありません).各タスクが他のタスクから完全に独立している場合、これは適切である可能性があります.したがって、タスクは相互の実行に影響を与えません.例えば、ウェブサーバでは、このキューは、瞬時バーストのスムーズ化に役立ちますが、これは、タスクが平均到達速度が処理可能速度を超える場合に、ワークキューは無制限に増加する可能性があります.maximumPoolSizes
よりも多くのスレッドスケジューリング時間を提供することができる.小さなキュー列を使用すると、通常より大きなプールサイズが必要となり、CPUがより多忙になるが、許容できないスケジューリングオーバーヘッドが発生し、スループットが低下する可能性がある.タスクをコミットするには、次の2つの方法があります.1つは、戻り値のないタスクをコミットする方法です.
ExecutorService executor = new TreadPoolExecutor(5, 10, 10L,TimeUnit.SECONDS,new BlockingArrayQueue(40));
Runnable handler = ......;
executor.execute(handler);
タスクの実行結果を取得する必要がある場合は、submitメソッドを呼び出してタスクをコミットできます.コードは次のとおりです.
ExecutorService executor = new TreadPoolExecutor(5, 10, 10L,TimeUnit.SECONDS,new BlockingArrayQueue(40));
Callable handler = ......;
Future future = executor.submit(handler);
2.7スレッドプールの完全なインスタンスの作成
import java.util.concurrent.*;
public TreadPoolTest(){
public static void main(String[] args){
ExecutorService executor = new TreadPoolExecutor(5, 10, 10L,TimeUnit.SECONDS,new ArrayBlockingQueue(40));
for(int i = 0;i < 10; i++){
Handler handler = new Handler();
executor.execute(handler);
}
executor.shutdown();
}
class Handler extends Runnable{
public void run(){
for(int i = 0; i < 5;i++){
System.out.println(i);
}
}
}
}
3 Executorのファクトリメソッドによるスレッドプールの作成
ExecutorsはJUCパッケージの下にあるファクトリクラスで、スレッドプールの作成に便利な方法を提供しています.
3.1固定サイズスレッドプールの作成
Executorsクラスを使用して、固定サイズのスレッドプールを作成できます.
ExecutorService executor = Executors.newFixedThreadPool(10);
固定サイズのスレッドプールは、ThreadPoolExecutorコアスレッド数と最大スレッド数を等しく設定することで実現できますが、実際にはソースコードもそうです.
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
一般に、スレッド数がコアスレッド数に達すると、スレッドの作成もスレッドの破棄も行われません.異常なスレッド終了が発生したり、allowCoreTimeOutを設定したりしない限り、keepAliveパラメータが0に設定されているため、スレッドの作成と破棄が頻繁になる可能性があります(ここでは推測のみです.検証が必要です).
3.2キャッシュ可能なスレッドプールの作成:
maximumPoolSize
を使用してキャッシュ可能なスレッドプールを作成できます.現在のスレッドプールの規模が処理要件を超えている場合、空のスレッドが回収されます.需要が増加すると、スレッドの数が増加します.スレッドプールの規模に制限はありません.コードは次のとおりです.ExecutorService executor = Executors.newCachedThreadPool(10);
前述したように、
maximumPoolSizes
パラメータは、Executors.newCachedThreadPool
のように実質的に境界のない値に設定することができ、実際にはソースコードも同様に実現される.public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
SynchronousQueue());
}
3.3単一スレッドのスレッドプールの作成
単一スレッドのExecutorを
maxmumPoolSize
で作成し、タスクがシリアルで実行されることを確認します.ExecutorService executor = Executors.newSingleExecutor();
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
}
4まとめ
この記事では、スレッドプールの作成方法、各パラメータの意味と役割について詳しく説明しますが、スレッドプールには、上記のほかにスケジューリング可能なスレッドプールがあります.このシリーズの次の記事では、このスレッドプールについて説明します.