Java スレッド プールの ThreadPoolExecutor ツール クラスについて

 

目次

ThreadPoolExecutor のコンストラクター

 スレッドプールに関するいくつかの補足

スレッドプールの動作原理の分析

概念と原理の説明

全体の流れは以下の通りです。

少し補足


スレッド プールを作成するには、主に次の 2 つの方法があります。

  • Executor ファクトリ クラスを介して作成され、作成方法は比較的単純ですが、カスタマイズ機能は限られています
  • ThreadPoolExecutor によって作成され、作成方法はより複雑になりますが、カスタマイズ機能は強力です

ただし、通常、Executor ファクトリ クラスを使用してスレッドを作成することはお勧めしません。

理由は次のとおりです。

Executor によって提供される多くのメソッドは、デフォルトで無制限の LinkedBlockingQueue を使用します. 高負荷条件下では、無制限のキューは簡単に OOM につながる可能性があります— OOM は「メモリ不足」の略で、メモリが使い果たされていることを意味します. このエラーは、JVM にオブジェクトにスペースを割り当てるのに十分なメモリがなく、ガベージ コレクターに再利用するスペースがない場合にスローされます。

その後、OOM が発生すると、すべての要求を処理できなくなり、致命的な問題になります。制限のないキューの使用を避けるようにする必要があります。つまり、Executor を慎重に使用してスレッドを作成します。

それでは、推奨される ThreadPoolExecutor コア ツール クラスについて詳しく学びましょう。

ThreadPoolExecutor のコンストラクター


ThreadPoolExecutor のコンストラクターは非常に複雑です。次のコードに示すように、最も完全なコンストラクターには 7 つのパラメーターがあります。
 

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

これら7つのパラメータの意味を見てみましょう!

corePoolSize

コア スレッドの数は、スレッド プールによって保持されるスレッドの最小数です。

最大プールサイズ

最大スレッド数

keepAliveTime と単位 

まず、スレッドがアイドル (ブロックおよび待機) とビジー (タスクの実行) の 2 つの状態に分けられることを知る必要があります。スレッドが長時間アイドル状態で、スレッド プール内のスレッドの数がその数より多い場合コア スレッド corePoolSize のうち、スレッドは破棄され、リサイクルされます

keepAlive と unit は、アイドル時間を測定するために使用されます. アイドル時間が keepAliveTime (単位は keepAliveTime は時間の単位です) よりも大きく、スレッドの数が corePoolSize よりも大きい場合、スレッドはログオフされます

ワークキュー

ワーク キュー (実行可能なタスクを格納するためのブロッキング キュー Runnable Task)

スレッドファクトリー

このパラメーターを使用して、自分でスレッドを作成する方法を定義できます。たとえば、意味のある名前をスレッドに割り当てることができます。

ハンドラ

このパラメーターを介して、タスクの拒否戦略をカスタマイズできます. スレッド プール内のスレッドがビジーでワーク キューがいっぱいの場合 (ワーク キューはバウンド キューであることが前提です)、新しいタスクが追加されると、タスクをサブミットすると、スレッド プールは受け入れを拒否します。

拒否戦略については、handler パラメータで指定でき、ThreadPoolExecutor は次の 4 つの戦略を提供します。

  • CallerRunsPolicy: タスクを送信するスレッドは、タスクを単独で実行します
  • AbortPolicy: デフォルトの拒否ポリシーは RejectedExecutorException をスローします — 「これはランタイム例外です。エディターはキャッチを強制しないため、無視するのは簡単です。開発者は慎重に使用する必要があります (独自の拒否ポリシーを定義できます)。
  • DiscardPolicy: 異常な出力なしで直接タスクを破棄します
  • DiscardOldestPolicy: 最も古いタスクを破棄. 実際には、最も早くタスク キュー workQueue に入ったタスクを破棄し、新しいタスクをタスク キューに追加します。

 スレッドプールに関するいくつかの補足

スレッドは重いオブジェクトです。頻繁な作成と破棄は避ける必要があります

現在、業界でのスレッド プールの設計は、一般にプロデューサー/コンシューマー モデルを採用しています。


スレッド プールのユーザーがプロデューサーであり、スレッド プール自体がコンシューマーです。

たとえば、上記のタスク キュー workQueue (生成された Runnable タスクはこのキューに配置されます)、これらのタスクはスレッド プール内のスレッドによって実行 (消費) されます。

スレッドプールの動作原理の分析

概念と原理の説明

1. スレッドプールの作成 この時点でタスクの投入がない場合 (ブロッキングキュー workQueue が空の場合、この時点でデフォルトのスレッドプールにスレッドはありません。もちろん、この時点で prestartThread メソッドを呼び出すこともできます)。コアスレッドを事前に作成する時間

2.スレッド プールにスレッドがないか、スレッド プールに残っているスレッドがコア スレッドの数 corePoolSize よりも小さい場合、新しいタスクが送信されるたびに、スレッド プールは新しいスレッドを作成してタスクを処理します。送信済み (workQueue 内のキュー実行可能なタスクをブロック)。

このとき、スレッド プール内のスレッドは常に生きており、スレッドがアイドル状態であっても、アイドル時間が keepAliveTime を超えても、スレッドは破棄されません (この時点で、スレッド プール内のスレッドはコアスレッドの数 (corePoolSize)    

すると、このときスレッドは実行可能なタスク(Runnable task)がタスクキューにあるときだけ待って、書き換えて実行を開始する(ビジーになる、その前にそこでスレッドがブロックされている)――>これが理由です。スレッド プール キューはブロッキング キューを使用します)

3.スレッド プール内のスレッド数がスレッド コアの数 corePoolSize と等しく、タスク キューに空きがある場合、新しいタスクが送信されると、タスクはタスク キュー (workQueue) にキューイングされます。実行。送信したばかりのタスクを実行するための新しいスレッドを作成する代わりに。

このとき、前に作成したスレッドはキャンセルされず、ブロッキング キュー内のタスクを取得し続けます. タスク キュー内のタスクが空の場合、タスク (Runnable Task) が入れられるまでスレッドはブロックされます. . タスク キュー内。

これが、スレッド プールのタスク キューがブロッキング キューである必要がある理由です。

前に、Java のスレッド プールはプロデューサー/コンシューマー モデルであると述べました。スレッドのユーザーは、実行可能なタスク Runnable タスクを生成し、それをタスク キューに入れ、スレッド プール内のスレッドによって消費されます (実行タスクの) )

4.スレッド プール内のスレッド数が corePoolsize に等しく、タスク キュー workQueue がいっぱいの場合。

このとき、新しい Runnable Task が来ると、スレッド プールは新しいスレッドを作成してタスクを処理します。

スレッド数が maximumPoolSize に達するまで — 「タスクを処理するための新しいスレッドを作成し続けません。

これらの新しいスレッドは、現在のタスクの実行後に破棄されません。代わりに、タスク キュー内のタスクが実行されるときに、タスク キュー内の RunnableTask を実行します(この時点でスレッドはビジー状態であり、アイドル状態ではありません)。となる判定ロジック――「スレッドを破棄する必要があるかどうかを判定する。タスクキューが空であるためにkeepAliveTimeよりも長くスレッドがブロックされる(アイドル状態になる)と、スレッドは破棄される――」までスレッド数は corePoolSize に等しい

5. スレッド プール内のスレッド数が maximumSize に達し、この時点でタスク キューもいっぱいになっている場合。

この場合、新しいタスクが来ると、拒否されたプロセッサによって直接処理されます。デフォルトのハンドラー ロジックは、RejectedExecutionException をスローすることです。

全体の流れは以下の通りです。

少し補足

スレッド プールを使用する場合は、例外処理にも注意する必要があります。たとえば、 ThreadPoolExecutor オブジェクトの execute() メソッドを介してタスクをサブミットする場合、タスクの実行中にランタイム例外が発生した場合、タスクを実行しているスレッドは、しかし、最も致命的なのは、タスクが異常であるにもかかわらず、通知が届かないため、タスクが正常に実行されたと勘違いしてしまうことです。スレッド プールには例外処理用のメソッドが多数用意されていますが、最も安全で簡単な解決策は、すべての例外をキャッチしてオンデマンドで処理することです。

スレッド プールの作成に関するアイデアとアイデア:

参照:スレッド作成のオーバーヘッドとスレッド プール - 概要

おすすめ

転載: blog.csdn.net/weixin_61061381/article/details/129147938