ExecutorService
をRunnable
コンストラクターに渡すスレッドを実行するよりもThread
を使用する利点は何ですか?
ExecutorService
は、生のThread
のような下位レベルの抽象化に関連する複雑さの多くを抽象化します。タスクの成功または突然の終了(Runnable
またはCallable
で表される)を安全に開始、終了、送信、実行、およびブロックするメカニズムを提供します。
JCiP 、セクション6.2、馬の口からまっすぐ:
Executor
は単純なインターフェースかもしれませんが、多種多様なタスク実行ポリシーをサポートする非同期タスク実行のための柔軟で強力なフレームワークの基礎を形成します。デカップリングの標準的な手段を提供します タスク提出 から タスク実行、タスクをRunnable
として記述します。Executor
実装は、統計収集、アプリケーション管理、監視を追加するためのライフサイクルサポートとフックも提供します。 ... 通常、Executor
を使用することが、アプリケーションに生産者/消費者設計を実装する最も簡単な方法です。
並列処理の基盤となるインフラストラクチャの実装に時間を費やすのではなく(多くの場合、誤って多大な努力を払って)、j.u.concurrent
フレームワークを使用すると、代わりにタスク、依存関係、潜在的な並列処理の構造化に集中できます。大規模な同時アプリケーションの場合、タスクの境界を特定して活用し、j.u.c
、より専門的なソリューションを必要とする可能性がある真の同時実行性の課題のはるかに小さなサブセットに集中することができます。
また、ボイラープレートのルックアンドフィールにもかかわらず、 同時実行ユーティリティを要約したOracle APIページ には、それらを使用するための確かな引数が含まれています。
開発者は標準ライブラリクラスをすでに理解している可能性が高いため、アドホックコンカレントコンポーネントのAPIと動作を学ぶ必要はありません。さらに、コンカレントアプリケーションは、信頼性が高く、十分にテストされたコンポーネントで構築されている場合、デバッグがはるかに簡単です。
この SOに関する質問 は、すぐにJCiPである良い本について尋ねます。まだ入手していない場合は、コピーを入手してください。そこに示されている並行性に対する包括的なアプローチは、この問題をはるかに超えており、長期的には多くの心痛を軽減します。
私が見る利点は、いくつかのスレッドを管理/スケジュールすることです。 ExecutorServiceを使用すると、バグに悩まされる可能性のある独自のスレッドマネージャーを記述する必要がありません。これは、プログラムが一度に複数のスレッドを実行する必要がある場合に特に便利です。たとえば、一度に2つのスレッドを実行する場合、次のように簡単に実行できます。
ExecutorService exec = Executors.newFixedThreadPool(2);
exec.execute(new Runnable() {
public void run() {
System.out.println("Hello world");
}
});
exec.shutdown();
この例は些細なことかもしれませんが、「hello world」行は重い操作で構成されており、プログラムのパフォーマンスを改善するために、その操作を一度に複数のスレッドで実行したいと考えてください。これはほんの一例です。複数のスレッドをスケジュールまたは実行し、スレッドマネージャーとしてExecutorServiceを使用したい場合がまだ多くあります。
単一のスレッドを実行する場合、ExecutorServiceを使用する明確な利点はありません。
従来のスレッドからの次の制限は、Executorフレームワーク(組み込みのスレッドプールフレームワーク)によって克服されます。
StackOverflowException
例外が発生するため、JVMがクラッシュします。スレッドプールの利点
スレッドプールを使用すると、要求またはタスクの処理中にスレッドが作成されないため、応答時間が短縮されます。
スレッドプールを使用すると、必要に応じて実行ポリシーを変更できます。 ExecutorServiceの実装を置き換えるだけで、単一のスレッドから複数のスレッドに移行できます。
Javaアプリケーションのスレッドプールは、システムの負荷と利用可能なリソースに基づいて決定された構成された数のスレッドを作成することにより、システムの安定性を高めます。
スレッドプールは、アプリケーション開発者をスレッド管理から解放し、ビジネスロジックに集中できるようにします。
以下にいくつかの利点を示します。
新しいスレッドを作成するのは本当にそんなに高価ですか?
ベンチマークとして、Runnable
sと空のrun()
メソッドで60,000個のスレッドを作成しました。各スレッドを作成した後、すぐにそのstart(..)
メソッドを呼び出しました。これには、約30秒の激しいCPUアクティビティが必要でした。 この質問 に対応して、同様の実験が行われました。これらの要約は、スレッドがすぐに終了せず、アクティブなスレッドが多数(数千)蓄積すると、問題が発生することです:(1)各スレッドにスタックがあるため、メモリが不足します、(2)OSによって課されるプロセスごとのスレッド数に制限があるかもしれませんが、 必ずしもそうではないようです 。
ですから、私が見る限り、1秒あたり10個のスレッドを起動することについて話している場合、それらはすべて新しいスレッドが起動するよりも速く終了し、このレートを超えないことを保証できます。目に見える性能や安定性に具体的な利点はありません。 (特定の並行処理のアイデアをコードで表現する方が便利で読みやすいかもしれませんが。)一方、実行に時間がかかる1秒あたり数百または数千のタスクをスケジュールする場合、大きな問題にぶつかります。すぐに。これは予期せずに発生する場合があります。サーバーへのリクエストに応じてスレッドを作成し、サーバーが受信するリクエストの強度が急上昇した場合。しかし、例えばすべてのユーザー入力イベント(キーを押す、マウスの動き)に応答する1つのスレッドは、タスクが短い限り、まったく問題ないようです。
ExecutorServiceは、FutureTaskへのアクセスも提供します。FutureTaskは、完了したバックグラウンドタスクの結果を呼び出しクラスに返します。 Callableを実装する場合
public class TaskOne implements Callable<String> {
@Override
public String call() throws Exception {
String message = "Task One here. . .";
return message;
}
}
public class TaskTwo implements Callable<String> {
@Override
public String call() throws Exception {
String message = "Task Two here . . . ";
return message;
}
}
// from the calling class
ExecutorService service = Executors.newFixedThreadPool(2);
// set of Callable types
Set<Callable<String>>callables = new HashSet<Callable<String>>();
// add tasks to Set
callables.add(new TaskOne());
callables.add(new TaskTwo());
// list of Future<String> types stores the result of invokeAll()
List<Future<String>>futures = service.invokeAll(callables);
// iterate through the list and print results from get();
for(Future<String>future : futures) {
System.out.println(future.get());
}
最大しきい値の制限なしに多数のスレッドを作成すると、アプリケーションのヒープメモリが不足する可能性があります。そのため、ThreadPoolを作成する方がはるかに優れたソリューションです。 ThreadPoolを使用すると、プールして再利用できるスレッドの数を制限できます。
エグゼキュータフレームワークは、Javaでスレッドプールを作成するプロセスを容易にします。 Executorsクラスは、ThreadPoolExecutorを使用してExecutorServiceの簡単な実装を提供します。
出典:
Java 1.5バージョンでは、Thread/Runnableは2つの別個のサービス用に設計されていました
ExecutorServiceは、Runnable/Callableを作業単位として指定し、Executorを(ライフサイクルを使用して)作業単位を実行するメカニズムとして指定することにより、これら2つのサービスを分離します。