Javaの並行処理の研究ノート----スレッドプール

序文

Javaスレッド・プールについての学習についての録音ノートは、からの主な内容の「Java並行プログラミングのアートは、」自分の理解と実践的な問題にいくつかの契約を追加します。

1.スレッドプールのコンセプトと利点

1.1なぜ私たちは、スレッドプールが必要なのか

同じスレッドがシステムリソース(クラスのロード、ガベージコレクションを)消費するオブジェクト、オブジェクトの作成と破棄が必要です。頻繁にスレッドを作成し、システムリソースを消費し、システムの安定性を減らします統一されたディストリビューション、チューニングおよび監視することができ、スレッドプールのスレッドを使用してください。

1.2利点

  • リソース消費量を削減スレッドを再利用することにより、スレッドの作成とリソースの消費量の破壊を保存するために作成されています。
  • 増加速度に対応しますときにジョブ投入、あなたは、スレッドの作成を待機する時間を減らすこと、タスクを実行するスレッドプールを使用することができます。
  • 横ばいの仕事多数のタスク、スレッドプールによってスケジューリングポリシー、キューに最初のタスクの到着は、システム上の巨大な圧力を避けるために、同時に実行されるスレッドの数を制御する場合。
  • 監視と優れたを調整します。

2.スレッドプールのスケジューリングプロセス

スレッドプールスケジューリングプロセスは、主に3つのコンセプトを含む:核心线程池任务队列および最大线程池、これらの3つの概念に対応するそれぞれのプール構成パラメータをスレッドcorePoolSizeworkQueueおよびmaximumPoolSizeスレッドプール、スレッドプールの処理フローに新しいタスクを送信示す下側の図と併せて:

(1)第一相(ウォームアップフェーズ):コアスレッドプール

プール内のスレッドの数はスレッドかどうかを判断するために核心线程大小、適合しない新しいスレッドが作成されたタスクを実行する;達し、その後、(2)ターン
:説明

  • テストした後、わずかにかかわらず、スレッドは(非アクティブスレッドが最大生存時間が破壊される達する)がアクティブであるかどうかの、スレッドの数を見てください。
  • 0のコアスレッドプールのサイズは、コアスレッドプールのサイズと同等であればテストの後、提出する作業であり、その後、キューに置かれている、1で新しいスレッドを作成します。

(2)第2段階:ワークキュー

決意队列是否已满、タスクは以下になりますキューの中、フル、(3)に移動します。

  • また、アンバウンド形式のキューを使用しないようにキュー帳試みを必要と、メモリリークの原因となるおそれがあります。

(3)第3段階:最大スレッドプール

プール内のスレッドの数はスレッドがかどうかを判断するために最大线程池数目、適合しない新しいスレッドが作成されたタスクを処理するために、達成するために、対応する取るタスクが政策を否定します

  • 拒否は、タスクが拒否されなければならないことを言っていない、スレッドプールにタスクへの参加を拒否を意味していました。

3.スレッドプール

3.1スレッドプールが作成されます

JDKはExecutorServiceのインターフェースタスクの実行を定義し、実装クラスThreadPoolExecutorを提供します。サンプルを作成します。

BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1);
ExecutorService executorService = new ThreadPoolExecutor(0, 1, 60, TimeUnit.SECONDS, 
        queue, new ThreadFactoryBuilder().build(), new ThreadPoolExecutor.CallerRunsPolicy());
复制代码

3.2ジョブの投入

executeノーリターン値のために提出されたタスク、submitタスクを送信する(コーラブル、結合フューチャー)の値を返します。例:

ExecutorService executorService = Executors.newFixedThreadPool(3);
executorService.execute(() -> {
    System.out.println("HelloWorld1");
});

Future<String> future = executorService.submit(() -> {
    return "HelloWorld2";
});
try {
    System.out.println(future.get());
} catch (InterruptedException | ExecutionException ex) {
    ex.printStackTrace();
}
复制代码

3.3スレッドプールが閉じ

あなたは使用することができますshutdownし、shutdownNowスレッドプールを閉鎖します。
実際のテスト:

  • シャットダウンは、アクティブなスレッドを終了しません。
  • shutdownNowのは例外:InterruptedExceptionをキャプチャするために、スレッドのニーズを割り込み信号のアクティブスレッドを送信します。

3.4スレッドプールの監視

ThreadPoolExecutorもAPIがロックする必要があることに注意することは、モニタにスレッドプールAPIの状態を各種取り揃えています。共通APIは以下のとおりです。

  • getActiveCount:アクティブなスレッドの数を取得します。
  • getPoolSize:スレッドプール内のスレッドの数を取得します。
  • getLargestPoolSize:スレッドプール内のスレッドの記録数
  • getTaskCount:スレッドプールは、タスクの合計数を提出します
  • getCompletedTaskCount:完了したタスクの数

4.詳細なパラメータコアスレッドプール

ことではThreadPoolExecutorと、スレッドプールのクラスを作成することができるExecutorServiceインタフェースを実装するクラス。

4.1パラメータリスト

ThreadPoolExecutorクラスは、以下のように、複数のコンストラクタメソッド、前記パラメータ最も完全なコンストラクタを提供含むパラメータcorePoolSize(コアスレッドプールのサイズ)を、maximumPoolSize(スレッドプール内のスレッドの最大数)、 keepAliveTimeアイドル・スレッド生存時間)、 unitアイドル・スレッドプール生存時間単位) 、workQueue(スレッドプールタスクキュー)、 threadFactoryスレッド作成工場)、 handlerタスクがポリシーを拒否)

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
复制代码

4.2 corePoolSize(コアスレッドプールの数)

スレッドプールの最初の段階は、ウォームアップ段階として理解することができ、それはである第一のコアスレッドプール満杯である(スレッドプール内か否かをアイドル状態のスレッド、スレッドのコア数は、午前1時00分に相当します)。
あなたはprestartAllCoreThreads()メソッドを実行する場合は、直接、すべてのコアスレッドを作成して開始します。

((ThreadPoolExecutor) executorService).prestartAllCoreThreads();
复制代码

理解

スレッドのコア数は、コアスレッドプールとして、小さすぎてはならない休止状態またはそれ以降のすべてのタスクが待ち状態であるになるだろう書き込みRedisのタイムアウトは、(作業キューが大きい)がある場合、実行スレッドは、考えることができ、1でありますコアスレッドプールセットの数CPUのコア数ので、ウォームアップ相を確保することは、コアごとに1つのスレッドを実行することができます。(タスクスレッドがCPUにマッピングされ、スレッドにマップ)

4.3ワークキュー(ジョブキュー)

作業キューが実行されるのを待っているキューを遮断する作業を格納するために使用されて、あなたはどのような現実の世界のシナリオに基づいてキューの意思決定のを選択することができます。
阻塞队列スレッドキューがいっぱいになったとき、キュー要素に入れ、手段がブロックされます。ブロックされたキューから要素を取ったときにスレッドキューが空の場合。主な焦点は、次のオプションのキューがあります。

  • ArrayBlockingQueue:キューを遮断囲まれ、FIFO
  • LinkedBlockingQueue:あなたは最大容量を設定し、有界FIFOにすることができ、キューをブロック
  • SynchronousQueue:参照してください。5.2.2
  • PriorityBlockingQueue

理解

作業キューを使用しようとすべきである有界キューを、キュー無制限1は、メモリリークにつながる可能性があり、第二位のスレッドプールが無効なパラメータです。

4.4 maximumPoolSize(スレッドの最大数)

スレッドプール内のスレッドの最大数は、場合にのみ、作業待ち行列が有界と容量が限られている場合には、引数が理にかなっています。キューが大きい場合、それはタスクがキューに配置されているが、実行するための新しいスレッドを作成しませんつながります。

理解

まず、作業キューは、CPUリソースをフルに活用する追加のスレッドプールの設定の最大数は、長すぎるタスク待ちを避ける意味、有効囲まれただけで、プール内のスレッドの最大数。

4.5 threadFactory(スレッドファクトリ)

例えば、ThreadFactoryBuilderはあなたが一般的にデフォルトを使用することができ、すぐに定義されたスレッドプールのスレッド名からスレッドをセットアップに提供guauaパッケージをグーグルことができます使用することができますスレッドファクトリを作成すると、指定するために使用しました。

new ThreadFactoryBuilder().build()
复制代码

4.6のRejectedExecutionHandler(拒否ポリシー)

すなわち、第三段階の後、スレッドプールは、スレッドの数がスレッドの最大数に達したときにスレッドプール、拒否ポリシー強制タスクに追加されるように、その後、タスクが拒否されます。
共通の戦略を拒否し、次のとおりです。

  • AbortPolicy:直接スロー例外
  • CallerRunsPolicy:スレッドを提出のタスク(タスクを提出する位置に挿入されたコードの実装に相当する位置)から直接タスクを実行
  • DiscardOldestPolicy:タスクキューに参加する最初のを破棄し、現在のタスクが追加されました。
  • DiscardPolicy:破棄

理解

個人的にはない、特にCPU資源タスク場合は、使用CallerRunsPolicy戦略は、より多くのタスクが実行されることを保証するために、適切なと感じています。

4.7 keepAliveTimeが及びTIMEUNIT(生存時間)

スレッドプールがアイドル状態にあるときにスレッドを破壊する特定の時間を超え、通常は一般的な設定は、60秒の生存時間です。

5.スレッドファクトリ

エグゼキュータファクトリクラスの典型的なスレッドプールの詳細な説明に続いて、より一般的なスレッドプールを作成するためのAPIを提供します。

5.1 FixedThreadPool(スレッドプールの固定された数)

5.1.1ソース

執行は、2 FixedThreadPoolはそれぞれ、APIを作成して提供します

public static ExecutorService newFixedThreadPool(int nThreads);
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) ;
复制代码

最初のソースはAPIを次の

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
复制代码

5.1.2特長

観測ソースを分析することができ、FixedThreadPoolは、次の特性があります。

  • (1)コアの数とスレッドのスレッドプールの最大数に等しいスレッドプールスケジューリングの第3段階に入ったことがありません
  • (2)スレッドの生存時間は、0であるアイドル・スレッドがすぐに破壊されます
  • タスクが拒否されないことを(3)アンバウンド形式のブロッキングキュー、キューOOMは異常があるかもしれません
  • (4)スレッドの数は、CPUコアの数よりも大きい場合、タスク到着(CPU集約的なタスク)の速度よりもタスク処理速度の大きい等しく、CPUを再生させることができます。

5.1.3アプリケーションのシナリオ

FixedThreadPool最大の特徴は、スレッドプール内のスレッドの最大数を制限することで、タスクのためのより適切なシステムリソースの消費も大きく、重い負荷のサーバの。

5.2 CachedThreadPool

5.2.1ソース

執行ファクトリクラス2 CachedThreadPool作成APIを提供します。

public static ExecutorService newCachedThreadPool();
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory);
复制代码

最初のAPIのソースは次のとおりです。

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}
复制代码

5.2.2 SynchronousQueue(同期キュー)

SynchronousQueueが呼ばれる无缓冲阻塞队列二つのスレッド間の要素の転送のために。

SynchronousQueueをテストした後、次の特徴があります。

  • (1)PUTとテイクは、スレッドが、同じプランを持って取ることができない、別のスレッドでなければなりません。要素は、出口にテイクputメソッドの後に申し出なければなりませんように理解することができ、それは同じスレッドに提供、テイクを持っているではありません。
  • いつでも(2)が決定キューがいっぱいである、remainingCapacity()メソッドは常に0を返します。このようなキューに要素を追加するために直接使用するaddメソッドとして、java.lang.IllegalStateExceptionをスローします:キューがいっぱい。私は、第二段階なしで、直接、第三段階に作業キュースレッドプールとしてSynchronousQueueを使用しています。このため、それがあると思います

5.2.3特長

得られた観測源を分析することができる、CachedThreadPoolは、以下の特性を有します。

  • SynchronousQueueを使用して(1)作業キュー、作業キューが常に満杯で判断し、それはスレッドの最大数まで、スレッド・プールのフルコアの後にスレッドを作成するときに話を停止し、ないだろう第二段階をスキップ
  • (2)(1当量)コアスレッドプール番号0、スレッドの最大数が最大の整数であり、タスクが送信されたときにコアスレッドプールが満杯であるので、それは(非アクティブスレッドの使用を優先し、スレッドが非アクティブに60年代になるという生存時間は、直接、新しいスレッドを作成しますアクティブなスレッドがありません)タスクを実行するために、新しいスレッドを作成しません。場合は、タスクの到着率がレートタスク処理よりも大きい時、そこにされたCPUを再生することができる、あまりにも多くのスレッドを作成します

5.2.4アプリケーションのシナリオ

係るCachedThreadPool特性は、多数の治療に適している、分析することができる短期割り当て、または軽負荷のサーバを。(スレッドシーンの数を制限するために主に必要)

5.3 SingleThreadExecutor(単一スレッドプール)

5.3.1ソース

(1)SingleThreadExecutor源として
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
复制代码

それはThreadPoolExecutorでFinalizableDelegatedExecutorServiceをパッケージの別の層を見ることができるように。

(2)FinalizableDelegatedExecutorServiceソース次のように
static class FinalizableDelegatedExecutorService extends DelegatedExecutorService {
    FinalizableDelegatedExecutorService(ExecutorService executor) {
        super(executor);
    }
    protected void finalize() {
        super.shutdown();
    }
}
复制代码

FinalizableDelegatedExecutorServiceは、次の特性があります。

  • 継承DelegatedExecutorService(プロキシスレッドプール)
  • オブジェクトがスレッドプールを閉鎖するためのイニシアチブを回復したとき、ファイナライズ方法を書き直し
(3)DelegatedExecutorServiceソース次のように
static class DelegatedExecutorService extends AbstractExecutorService {
    private final ExecutorService e;
    DelegatedExecutorService(ExecutorService executor) { e = executor; }
    public void execute(Runnable command) { e.execute(command); }
    public void shutdown() { e.shutdown(); }
    public List<Runnable> shutdownNow() { return e.shutdownNow(); }
    public boolean isShutdown() { return e.isShutdown(); }
    public boolean isTerminated() { return e.isTerminated(); }
    public boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException {
        return e.awaitTermination(timeout, unit);
    }
    public Future<?> submit(Runnable task) {
        return e.submit(task);
    }
    public <T> Future<T> submit(Callable<T> task) {
        return e.submit(task);
    }
    public <T> Future<T> submit(Runnable task, T result) {
        return e.submit(task, result);
    }
    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException {
        return e.invokeAll(tasks);
    }
    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                         long timeout, TimeUnit unit)
        throws InterruptedException {
        return e.invokeAll(tasks, timeout, unit);
    }
    public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException {
        return e.invokeAny(tasks);
    }
    public <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                           long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
        return e.invokeAny(tasks, timeout, unit);
    }
}
复制代码

DelegatedExecutorServiceを見ることができます。

  • コンストラクタスレッドプールオブジェクトを渡す必要があり、この方法は、方法を実行するペアで渡された内部スレッドプールオブジェクトをマップは、に相当するラッパークラス剤
  • この方法は、DelegatedExecutorServiceは関係なく、特定のタイプのスレッドプールをラップされている、あなたがその特定のメソッドを実行することができない、唯一のパッケージを提供し実行することができるだけ提供ExecutorServiceの包装方法を実行することができます

5.3.2 SingleThreadExecutor特長

そして、内部結合FinalizableDelegatedExecutorService ThreadPoolExecutor SingleThreadExecutorは、次のような特性を得ることができます:

  • コアの数とスレッドの最大数は、スレッドプールスレッドプールは固定され、全ては1です。また、彼が言ったことを、いつでもタスクが実行されます
  • アイドル・スレッド生存時間は、アイドルスレッドはすぐに破壊される意味し、0です。
  • それはGC回収されたときに、スレッドプールスレッドプールのスレッドが自動的にシャットダウンします

5.3.3アプリケーションのシナリオ

SingleThreadExecutorタスクの順序を実行する必要がありますが、それに対応並行処理の量を低減させるシナリオに適用される、QPSが削減されます。

スレッドプールの6.使用の合理化

6.1コアスレッドプールのサイズ及びスレッド構成の最大数

メインの設定スレッドプールのサイズが懸念されます任务类型任务执行时间机器负载

(1)タスクの種類を

リソースのタスクのタスクの消費によるとに分けることができCPU密集型任务IO密集型任务混合型任务
共通のコード処理(計算、判定ロジック)CPU集中型のタスクとIO集中的なタスクへのファイルの読み書き(印刷ログ)ネットワーク使用状況など属しタスク。

  • CPU密集型任务CPU集中型のタスクのために、それが推奨され、それが少ないCPUコアの数よりもされ、その結果、スレッドの最大数を減らすために、1スレッドの切り替えあまりにも多くのスレッドが頻繁文脈で、余分なコストをもたらすが、フル稼働CPUを作るのは簡単だろう。
  • IO密集型任务IO集中型のタスクのために、推奨されます(例えば2 * CPUのコア数など)の数のスレッドを改善するために、多くの場合、アクティブなスレッドが読み込まれ、書き込みIOとCPUが成立しないようように、あなたは他のスレッドを処理するCPUを利用することができます。
  • 混合型任务CPUとIO時間のタスクによると、あなたは、タスクを分解することができるかどうかを確認する際、CPUとIO倍以下のタスク、並行アップグレード大型の分解の程度。

(2)タスクの実行時間

通常の状況下では、スレッドプールを使用してタスクのクラスは、あなたがあまりにも長い短いタスク待ちを避けるため、CPUを取得する際にタスク間でより公平にすることができます。
インパクトことに留意すべき任务执行时间タスク自体のほかには、多くの場合、特別な注意が必要になり休眠操作任务依赖性

  • 长任务タスクが長時間実行されたときに、推奨されるスレッドの最大数を増やすある程度するタスクの後ろに待機時間を短縮しつつ、並行性(より少ない状況下でCPUコアの数よりもスレッドの数)を増加させる(ただし、スレッド増加の間にCPUスイッチによる特定のオーバーヘッド)
  • 短任务タスクの実行時間が短い場合、推奨されるスレッドの数を減らすためにより高速の実装以来、スレッド少量のタスクにまですることができ、そしてタスクが突然に起因できるスレッドの数が少ないため、とき増加CPUが再生される防ぎます
  • 任务依赖性タスクがある場合はRedisの、データベースや他の操作へのアクセスを、特別な注意が必要な他のモジュールまたはコンポーネントへの依存度は、タスクの実行時間の急激な増加につながる可能性が(1)まず、0または1にコアスレッドプールのセットを回避する唯一のコアスレッド、タスクのタイムアウトは、すべてのタスクの後続実行を遅らせる原因となる場合、(2)実際の状況は、できる適度最大スレッドプールを増加させます
  • 休眠操作あなたは、特定の状況下で任務にスリープ状態に、その後必要な場合(非常に推奨休止状態を必要に応じて、断続的な睡眠をお勧めしますし、最大スリープ時間を設定する)、増加したタスクの実行時間になります。そして、同じことが、コアスレッドプールのサイズに注意を払います。

(3)マシンの負荷

負荷の大きいマシンでは、圧力は、スレッド・マシン・プールの数を制限することによって削減されます。影響机器负载要因は、一般的に机器配置、マシンが複数のサービスミックスの生地を持っている場合、マシンはより大きな圧力につながります。

  • 机器配置マシン構成が低い場合、番号は、サーバー・スレッド・プールへの圧力を減らすために削減することができます。
  • 服务混布通常の状況下では、スレッドの数を減らし、同じタスクの圧力で時に複数のサービスを検討するために、混合生地をサービスミックスの生地を避けます。
  • 线程池的种类和数目これらのプールで複数のスレッドプールのスレッドがフル稼働している複数のタスクが存在する場合だけでなく、サービスの同じ種類のサービス・ミックス・布・マシンの圧力に影響を与えるだろう、マシンは、タスクスパートの特定の種類があるかもしれない、全体的な圧力サージとなり、タスクの実行の他の種類の偏見。

6.2その他の設定

  • 使用してみてくださいスレッドプールのカスタマイズを使用すると、ポリシーをカスタマイズすることができますので、。
  • ワークキューが推奨有界キュー(SO力へのスレッドの最大数のことを回避するOOMに)、キューの数が突然キューが満たされている防ぐために、可能な限り大きくすることができます。
  • ポリシーが推奨拒否CallerRunsPolicyは、タスクが実行されていることを確認。

6.3実用上の問題、例えば

(1)スレッドプールの数が小さすぎる+アクセスは、すべてのタスクの遅延処理で得られた、残業のRedisあります

質問:その時、スレッドプールのパラメータは十分に理解していない、コアスレッドプールのサイズが0に設定され、要求の数を作るタスク登場訪問Redisの残業現象は、例数でタスク実行遅延現象の多くがあったされていない状態。
解決コアスレッドプールスレッドプールおよび修正の最大数は1/2 CPUコア数となっている。Redisの接続プールの設定および変更、ある程度の再試行間隔を減少させ、接続タイムアウト時間のRedis:。

(2)ショートタスク要求+スパート、CPUが再生されます

問題点:その急激な増加のQPS(数ミリ秒、あるいは1ミリメートル要求数回)が、クライアントがエラーを使用しているため、ほぼ10分間続きました。サーバーのCPUが再生され、10分以内に、廃棄された大面積があり、サービスの特定の程度は使用できません表示されます。

分析:理論的には、スレッドプールは、リクエストにクリップすることができ、それでも発生するCPUは、現象を果たしました。理由の分析:

  • 。私は急激な増加の要求があり、短いタスク CPU集約型タスクという。
  • II。スレッドは、スレッドプールを要求スパート処理の最大数は、スレッドプールがNアクティブなスレッドで10分間かけていたことができるCPUコアの数(N)に達します。
  • III。マシンながら布サービスを混合し、サービスが存在するスレッドプールの多くの異なるタイプCPUを再生させることが最も時間の異常な要求の急激な増加は、クライアントの要求がスパートように、これらすべての要因を考えると、各CPUハンドル、合理的な要求が適切に処理されるか、または遅延処理することはできません。

ソリューション

  • サーバは、クライアントの処理要求流量制限を二次メッセージ、メッセージ妥当な速度制限の部分を破棄し、。
  • 各スレッドプールスレッドの最大数を減らし、サービスのための混合布、2/3 CPUコア数の制御に現在サービスのスレッドプールの最大数のすべてのスレッドがあります。

おすすめ

転載: juejin.im/post/5e784466f265da574b793818