Javaコンカレントプログラミング-実際の本番環境で使用されるスレッドプール(スレッドプールThreadPoolExecutorの原則とインタビューの質問)

問題

スレッドプールの長所と短所はどこにありますか?
スレッドプールの基本的な実装原則は何ですか?
日常の開発でスレッドプールをうまく活用するにはどうすればよいですか?
スレッドプールで実際に使用されているのはどれですか?

スレッドプールThreadPoolExecutor

コンセプト
スレッドプールによって行われる作業は、主に実行中のスレッドの数を制御することです。処理にタスクがキュー追加され、スレッドが作成された後にこれらのタスクが開始されます。最大数を超えると、超過したスレッドは順番待機し、他のスレッドを待機します。実行が完了すると、タスクは実行のためにキューから削除されます。

利点
彼の主な機能は、スレッドの再利用、同時実行の最大数の制御、およびスレッドの管理です。

  1. スレッドの再利用:新しいスレッドを保持したり、作成されたスレッドを再利用してスレッドの作成と破棄のオーバーヘッドを削減したり、システムリソースを節約したりする必要はありません。
  2. 応答速度の向上:タスクに到達したら、新しいスレッドを作成する必要はなく、スレッドプールのスレッドを直接使用します。
  3. 管理スレッド:同時実行の最大数を制御したり、スレッドの作成を制御したりできます。

システム
ExecutorExecutorServiceAbstractExecutorServiceThreadPoolExecutorThreadPoolExecutorこれは、スレッドプールによって作成されたコアクラスです。ツールと同様Arrays、独自のツールもありますCollectionsExecutorExecutors

アーキテクチャの実装

Javaのスレッドプールは、Executor、Executors、ExecutorService、およびThreadPoolExecutorクラスを使用するExecutorフレームワークを介して実装されます。
ここに写真の説明を挿入

スレッドプールの使用方法

スレッドプールを作成する3つの一般的な方法

newFixedThreadPoolLinkedBlockingQueue実装、固定長スレッドプールを使用します。

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

newSingleThreadExecutorLinkedBlockingQueue実装を使用します。プールにはスレッドが1つしかありません。

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
}

newCachedThreadPoolSynchronousQueue実装、可変長スレッドプールを使用します。

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

スレッドプールの7つのパラメータ

int corePoolSize,核心线程
int maximumPoolSize,非核心线程
long keepAliveTime,时间
TimeUnit unit,时间单位
BlockingQueue<Runnable> workQueue,队列
 ThreadFactory threadFactory,线程工厂
RejectedExecutionHandler handler 拒绝策略

パラメータ 意義
corePoolSize スレッドプール内の常駐コアスレッドの数
maximumPoolSize 収容できるスレッドの最大数
keepAliveTime アイドルスレッドの生存時間
単位 生存時間単位
workQueue 送信されたが実行されなかったタスクを格納するキュー
threadFactory スレッドを作成するためのファクトリクラス
ハンドラ 待機キューがいっぱいになった後の拒否ポリシー

メソッドノート

corePoolSize –アイドル状態であっても、プールに保持するスレッドの数。allowCoreThreadTimeOutが設定されていない限り

maximumPoolSize –プールに許可するスレッドの最大数keepAliveTime –スレッドの数がコアよりも多い場合、これは過剰なアイドルスレッドが終了する前に新しいタスクを待機する最大時間。
unit –keepAliveTime引数の時間単位workQueue–
タスクが実行される前にタスクを保持するために使用するキュー。このキューは、executeメソッドによって送信された実行可能タスクのみを保持します。
threadFactory –エグゼキュータが新しいスレッド
ハンドラを作成するときに使用するファクトリ–スレッドの境界とキューの容量に達したために実行がブロックされたときに使用するハンドラ

コード

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
    
    
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

理解:スレッドプールの作成パラメータは銀行のようなものです。

corePoolSizeたとえば、銀行の「オンデューティウィンドウ」と同じように、今日、顧客の要求(タスク)を処理する2人の窓口があります3人以上の顧客がいる場合、新しい顧客は待機エリア(待機キュー)で待機しますときに待合室がいっぱいです、「残業ウィンドウはこの時点でオープンする必要があり、作業残業に他の3つの出納係を許可する。このとき、最大ウィンドウは5です。すべてのウィンドウが開いていて、待機領域がまだいっぱいの場合は、この時点で「拒否戦略をアクティブにして、入らないように顧客の流入通知する必要があります。新規顧客の流入がなくなったため、仕事を終えた顧客が増え、ウィンドウがアイドル状態になり始めました。このとき、余分な3つの「残業ウィンドウ」をキャンセルして、2つの「デューティウィンドウ」に戻します。workQueuemaximumPoolSizehandlerkeepAlivetTime

処理する

実行中のスレッドの数がcorePoolSize未満の場合は、コアスレッドを作成し、corePoolSize以上の場合は、待機キューに入れます。

待機キューがいっぱいで、実行中のスレッドの数がmaximumPoolSize未満の場合は、非コアスレッドを作成します。maximumPoolSize以上の場合は、拒否戦略を開始します。

スレッドがkeepAliveTimeの期間何の関係もない場合、実行中のスレッドの数がcorePoolSizeより大きい場合、非コアスレッドは閉じられます。

スレッドプール拒否戦略

ここに写真の説明を挿入

待機キューがいっぱいになり、スレッドの最大数に達して新しいタスクが到着したら、拒否戦略を開始する必要があります。JDKは、それぞれ4つの拒否戦略を提供します。

  1. AbortPolicyRejectedExecutionExceptionシステムが正常に実行されないように例外を直接スローするデフォルトのポリシー
  2. CallerRunsPolicy:例外をスローしたり、タスクを終了したりすることはありませんが、タスクを呼び出し元に返します。
  3. DiscardOldestPolicy:キュー内で最も長く待機しているタスクを破棄してから、現在のタスクをキューに追加して、タスクの送信を再試行します。
  4. DiscardPolicy:処理せずにタスクを直接破棄します。

実際の生産で使用されるスレッドプールはどれですか(強調)?

Alijava開発マニュアルのSongshanバージョンからの画像
ここに写真の説明を挿入

単一、可変、および固定長は必要ありません理由は、FixedThreadPoolそしてSingleThreadExecutor底部層が使用されLinkedBlockingQueue、最大キュー長が達成されInteger.MAX_VALUE、明らかにOOMをもたらします。したがって、実際の本番環境では、通常ThreadPoolExecutor7つのパラメーターを渡し、スレッドプールをカスタマイズします。

ExecutorService threadPool=new ThreadPoolExecutor(2,5,
                        1L,TimeUnit.SECONDS,
                        new LinkedBlockingQueue<>(3),
                        Executors.defaultThreadFactory(),
                        new ThreadPoolExecutor.AbortPolicy());

カスタムスレッドプールパラメータの選択

CPUを集中的に使用するタスク

CPUを集中的に使用するタスクの場合、スレッドの最大数はCPUスレッドの数+1です。

CPUを集中的に使用するということは、タスクがブロックせずに多くの計算を必要とすることを意味します。CPUはフルスピードで実行されています
。CPUを集中的に使用するタスクは、真のマルチコア
CPU (マルチスレッドによる)とシングルコアCPU(悲劇? )、シミュレートされたマルチスレッドの数に関係なく、CPUの合計計算能力はそれだけであるため、タスクを加速することはできません。
CPUを集中的に使用するタスクは、可能な限り少ないスレッドを構成します。
一般式:CPUコアの数+1スレッドプール

IOを多用するタスク

IOを多用するタスクの場合は、できるだけ多くのポイントを割り当てます。これは、CPUスレッドの数* 2、またはCPUスレッドの数/(1-ブロッキング係数)になります。

大手工場の経験

IOを多用します。つまり、タスクには多くのO、つまり多くのブロッキングが必要です。
シングルスレッドで10を多用するタスクを実行すると、待機中に多くのCPUコンピューティングパワーが無駄になります。
したがって、IOを多用するタスクでマルチスレッドを使用すると、プログラムの実行を大幅に高速化できます。シングルコアCPUでも、この加速は主に無駄なブロッキング時間を利用します。
IOが集中すると、ほとんどのスレッドがブロックされるため、スレッド数を構成する必要があります。
参照式:CPUコア数/ 1-ブロッキング係数。
ブロッキング係数は
0.8〜09です。たとえば、8コアCPU:8 /(1-0.9)=80スレッド

スレッドプールの基本的な動作原理について話します(強調)

ここに写真の説明を挿入

次のコンテンツは非常に重要です、次のコンテンツは非常に重要です、次のコンテンツは非常に重要です

1.スレッドプールを作成した後、送信されたタスク要求を待ちます。
2. execute(メソッドを呼び出して要求タスクを追加すると、スレッドプールは次の判断を下します
 。2.1実行中のスレッドの数がcorePoolSize未満の場合は、すぐにタスクを実行するスレッドを作成します。2.2実行中のスレッドの数がcorePoolSize以上の
 場合このタスクをキューに入れます
 2.3この時点でキューがいっぱいで、実行中のスレッドの数がmaximumPoolSize未満の場合でも、このタスクをすぐに実行するには非コアスレッドを作成する必要があります
 2.4キューがいっぱいで、実行中のスレッドの数がmaximumPoolSize以上の場合、次に、スレッドプールは飽和拒否戦略を開始して実行します。3

スレッドがタスクを完了すると、キューから次のタスクを実行のために取得します。4 スレッドが特定の時間(keepAlive Time)を超えて何もしない場合その時点で、スレッドプールは判断します。
現在実行中のスレッドの数がcorePoolSizeより大きい場合、このスレッドは停止します。
したがって、スレッドプールのすべてのタスクが完了すると、最終的にcorePoolsizeのサイズに縮小されます。

CPUコア数の確認方法

マウスの右ボタン、いいえ!!!

System.out.println(Runtime.getRuntime().availableProcessors());

参照

Javaコンカレントプログラミング-スレッドプールThreadPoolExecutorは、
Aliボスを使用して、スレッドプール
JVM-JUC-Core Alijava開発マニュアルSongshanversion.pdfの基本原理を理解します

おすすめ

転載: blog.csdn.net/e891377/article/details/108737161