Javaコンカレント(基礎知識):Executorフレームワークおよびスレッドプール

5775 ワード

Javaコンカレント(基礎知識)--スレッドの作成、実行、停止の2つの方法について説明します.Threadクラスを直接継承し、Runnableインタフェースを実装してThreadに割り当てます.この2つのスレッドの作成方法は、スレッドが少ない場合は問題ありませんが、大量のスレッドを作成する必要がある場合は問題が発生します.この使用方法では、スレッド作成文がコードに勝手に散らばっているため、スレッドを統一的に管理することができず、作成スレッドの数を管理することができず、過剰なスレッド作成は直接システムをクラッシュさせます.
高集約の観点から、JDK 5のExecutorフレームワークである統一的な作成と実行インタフェースを作成し、これらのスレッドを管理する必要があります.
Executorフレームワーク
Javaクラスライブラリでは、タスク実行の主な抽象はThreadではなくExecutorです.このインタフェースは次のように定義されています.
public interface Executor {
    void execute(Runnable command);
}

Executorは単純なインタフェースですが、柔軟で強力な非同期タスク実行フレームワークの基礎を提供しています.このフレームワークは、タスクのコミットプロセスを実行プロセスとデカップリングする標準的な方法を提供するさまざまなタイプのタスク実行ポリシーをサポートします.
Executorは、生産者-消費者モードに基づいて、タスクをコミットする操作は生産者(完了するワークユニットを生成する)に相当し、タスクを実行するスレッドは消費者(実行ワークユニット)に相当する.1つのプログラムで生産者-消費者モデルを実現する場合、最も簡単な方法はExecutorを使用することです.
Executorインタフェースは、タスクをコミットする方法を定義しますが、閉じる方法を定義していません.ExecutorServiceインタフェースはExecutorインタフェースを拡張し、ライフサイクル管理の方法を追加しました.
public interface ExecutorService extends Executor {
    void shutdown();

    List<Runnable> shutdownNow();

    boolean isShutdown();

    boolean isTerminated();

    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;
  
    ...    
}

ExecutorServiceのライフサイクルには、実行、クローズ、終了の3つのステータスがあります.ExecutorServiceは、初期作成時に実行されます.shutdownメソッドでは、緩やかなクローズ・プロシージャが実行されます.新しいタスクは受け入れられず、コミットされたタスクの実行が完了するまで待機します.まだ実行が開始されていないタスクも含まれます.shutdownNowメソッドは、すべての実行中のタスクをキャンセルし、キュー内でまだ実行されていないタスクを開始しない乱暴なクローズプロセスを実行します.
すべてのタスクが完了すると、ExecutorServiceは終了状態になります.awaitTerminationを呼び出してExecutorServiceが終了状態に達するのを待つか、isTerminatedを呼び出すことでExecutorServiceが終了したかどうかをポーリングできます.通常、awaitTerminationを呼び出すとすぐにshutdownが呼び出され、ExecutorServiceを同期的に閉じる効果が得られます.
スレッドプール
Executorフレームワークのコアはスレッドプールです.スレッド・プールとは、同じワーク・スレッドのセットを管理するリソース・プールであり、「スレッド・プールでタスクを実行する」ことは、「タスクごとにスレッドを割り当てる」ことよりも優れています.新しいスレッドを作成するのではなく、既存のスレッドを再利用することで、複数のリクエストを処理するときに、スレッドの作成と破棄中に発生する大きなオーバーヘッドを割り当てることができます.もう1つの追加の利点は、リクエストが到着すると、作業スレッドが通常存在するため、スレッドの作成を待つことによってタスクの実行が遅延することがなく、応答性が向上することです.スレッドプールのサイズを適切に調整することで、プロセッサを忙しくさせるために十分なスレッドを作成できます.また、スレッドが競合しすぎてアプリケーションがメモリを消費して失敗することを防止できます.
ThreadPoolExecutorは、次のように宣言されたスレッドプールを定義します.
public class ThreadPoolExecutor extends AbstractExecutorService { ... }

public abstract class AbstractExecutorService implements ExecutorService { ... }

ThreadPoolExecutorはAbstractExecutorServiceから継承され、AbstractExecutorServiceはExecutorServiceインタフェースを実現しているので、ThreadPoolExecutorも間接的にExecutorServiceインタフェースを実現していることがわかります.
ThreadPoolExecutorは多くのコンストラクション関数を定義し、次のコードはクラスの最も重要なコンストラクション関数を示します.
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) { ... }

corePoolSize、maximumPoolSize、keepAliveTime、unitのパラメータは、スレッドプールの基本サイズ、最大サイズ、および生存時間をそれぞれ定義します.corePoolSizeは、スレッドプールの基本サイズ、すなわち、タスク実行時のスレッドプールのサイズがなく、ワークキューがいっぱいになった場合にのみ、この数を超えるスレッドが作成されるスレッドプールのターゲットサイズを定義します.maximumPoolSizeはスレッドプールの最大サイズを定義し、スレッドプールが同時にアクティブにできるスレッド数の上限を示します.keepAliveTimeとunitは、スレッドの生存時間を共に定義し、あるスレッドの空き時間が生存時間を超えた場合、回収可能とマークされ、スレッドプールの現在のサイズがほぼ大きい場合、このスレッドは終了する.
workQueueパラメータにはRunnableのブロックキューが含まれています.スレッドプールがほぼ小さい場合、新しくコミットされたタスクはこのブロックキューに格納されます.ブロックキューの実装には、無境界キュー、有界キュー、同期転送キューの3つが含まれます.
threadFactoryパラメータはスレッドを作成するファクトリを設定するために使用され、スレッドファクトリを通じて作成したスレッドごとにより意味のある名前を設定することができ、問題の位置決めを容易にすることができます.
handlerパラメータはスレッドプール飽和ポリシーを定義した.境界キューが満たされ、スレッドプールのアクティブなスレッドが最大スレッド数に達すると、飽和ポリシーが有効になります.JDKは、AbortPolicy、DiscardPolicy、DiscardOldestPolicy、CallerRunsPolicyのいくつかの異なるRejectedExecutionHandler実装を提供しています.AbortPolicyは、チェックされていないRejectedExecutionExceptionを放出するデフォルトの飽和ポリシーです.DiscardPolicyポリシーは、新しくコミットされたタスクを直接破棄し、DiscardOldestPolicyポリシーはキューの最初の最も古いタスクを破棄します.CallerRunsPolicyポリシーは、タスクを破棄したり、例外を放出したりすることなく、一部のタスクを呼び出し元に戻し、新しいタスクのトラフィックを低減する調整メカニズムを実現します.これにより、オンライン・プール内のスレッドで新しいコミットされたタスクを実行するのではなく、executeが呼び出されたスレッドでタスクを実行します.
Executorsスタティックファクトリメソッド
前節から、ThreadPoolExecutorの新規作成には多くのパラメータを入力する必要があり、非常に使いにくいことがわかりました.使いやすいように、Executorsはいくつかの静的ファクトリメソッドを提供し、スレッドプールの作成を大幅に簡素化しました.
  • newFixedThreadPool:newFixedThreadPoolは固定サイズのスレッドプールを作成し、タスクをコミットするたびにスレッドプールを作成し、スレッドプールの最大数に達するまでスレッドプールの規模は変化しません.
  • newCachedThreadPool:newCachedThreadPoolはキャッシュ可能なスレッドプールを作成し、スレッドプールの現在の規模が処理要件を超えた場合、空きスレッドが回収されます.需要が増加すると、新しいスレッドを追加できます.スレッドプールの規模には制限はありません.
  • newSingleThreadExecutor:newSingleThreadExecutorは、単一作業者スレッド実行タスクを作成する単一スレッドのExecutorです.このスレッドが異常に終了すると、別のスレッドが作成されます.

  • newCachedThreadPoolを例にとると,これらの静的工場法の内部実装を見ることができる.
    public static ExecutorService newCachedThreadPool() {
            return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                          60L, TimeUnit.SECONDS,
                                          new SynchronousQueue<Runnable>());
        }

    これらのスタティックファクトリメソッドは、最終的に呼び出されたThreadPoolExecutorの構造関数であり、スレッドプールの基本サイズが0、最大サイズがInteger値の上限、スレッドの生存時間が60 s、ブロックキューがSynchronousQueueであることがわかります.これらのパラメータから分かるように、スレッドがnewCachedThreadPoolのスレッドプールをコミットすると、基本サイズが0であるため、必ず基本サイズより大きくなり、タスクはブロックキューに入りますが、SynchronousQueueの内部には容量がなく、現在のスレッド数が最大スレッド数に達していないため、タスクはすぐに実行されます.タスクの実行には60 sのタイムアウト時間があり、この時間内に新しいタスク呼び出しがある場合、新しいタスクはこのスレッド上で直接実行されます.
    まとめ
    スレッドプールの使用は、スレッドを統一的に管理し、スレッドの管理性を向上させるのに役立ちます.マルチスレッドコードを書くときは、スレッドプール方式を優先的に使用してスレッドを作成する必要があります.