2024 Java インタビュー -- マルチスレッド (2)

シリーズ記事の目次

  1. 2024 Java インタビュー (1) – 春の章
  2. 2024 Java インタビュー (2) – 春の章
  3. 2024 Java インタビュー (3) – 春の章
  4. 2024 Java インタビュー (4) – 春の章
  5. 2024 Java インタビュー – コレクション
  6. 2024 年の Java インタビュー – redis(1)
  7. 2024 年の Java インタビュー – redis(2)


スレッドプール

1. スレッドプールを使用する

(最下層は run メソッドを実装します)

static class DefaultThreadFactory implements ThreadFactory {
    
    
    DefaultThreadFactory() {
    
    
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
        namePrefix = "pool-" + poolNumber.getAndIncrement() +"-thread-";
    }
    public Thread newThread(Runnable r) {
    
    
        Thread t = new Thread(group, r,namePrefix + threadNumber.getAndIncrement(),0);
        if (t.isDaemon()) t.setDaemon(false);  //是否守护线程
        if (t.getPriority() != Thread.NORM_PRIORITY) t.setPriority(Thread.NORM_PRIORITY); //线程优先级
        return t;
    }
}

利点:

1. 応答速度の向上(新しいスレッドの作成時間の短縮)

2. リソース消費を削減します (スレッド プール内のスレッドを再利用します。毎回スレッドを作成する必要はありません)。

3. スレッド管理を容易にする

利点: 作成されたスレッドを再利用することで、リソースの消費が削減され、スレッドがキュー内のタスクを直接処理して応答を高速化でき、統合された監視と管理が容易になります。


2. スレッドプールコンストラクター

/**
* 线程池构造函数7大参数
*/
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,
    TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,
    RejectedExecutionHandler handler) {
    
    }

パラメータの紹介:

パラメータ 効果
コアプールサイズ コア スレッド プールのサイズ、コア スレッドの数、つまり、通常の状況で作業を作成するスレッドの数。これらのスレッドは作成後に削除されず、常駐スレッドになります。
最大プールサイズ 最大スレッド プール サイズ、最大スレッド数。コア スレッドの数に対応し、作成できるスレッドの最大数を表します。たとえば、現在タスクが多く、コア スレッドの数が使い果たされている場合、要求を満たすことができない場合、新しいスレッドが作成されますが、スレッド プール内のスレッドの総数が最大スレッド数を超えることはありません。
キープアライブ時間 corePoolSize を超えるスレッド プール内のアイドル スレッドの最大生存時間。コア スレッドの数を超えるスレッドのアイドル生存時間、つまり、コア スレッドは削除されませんが、コア スレッドの数を超える一部のスレッドは削除されます。一定期間アイドル状態が続くと削除されます。削除するには、setKeepA7iveTime を通じてアイドル時間を設定します。
時間単位 keepAliveTime 時間単位
ワークキュー ブロッキング タスク キューは、実行されるタスクを保存するために使用されます。現在、コア スレッドがすべて使用されていると仮定します。まだ受信するタスクがある場合、それらはすべてキューに入れられます。キュー全体がいっぱいになるまで、タスクは続行されます。入力すると、新しいスレッドの作成が開始されます。
スレッドファクトリー 新しいスレッド ファクトリを作成します。これは、タスクを実行するスレッドを生成するために使用されるスレッド ファクトリです。デフォルトの作成ファクトリーを使用することを選択できます。生成されたスレッドはすべて同じグループに属し、同じ優先順位を持ち、デーモン スレッドではありません。もちろん、糸工場をカスタマイズすることもできますが、通常はビジネスに応じて異なる糸工場を開発します。
拒否された実行ハンドラ 拒否戦略。送信されたタスクの数が maxmumPoolSize+workQueue の合計を超えると、タスクは処理のために RejectedExecutionHandler に渡されます。タスク拒否戦略には 2 つの状況があります。1 つ目は、shutdown およびその他のメソッドを呼び出してスレッド プールを閉じるときです。この時、スレッドプールが内部であってもまだ未完了のタスクが実行されていますが、スレッドプールはクローズされているため、スレッドプールにタスクを投入し続けると拒否されてしまいます。もう 1 つの状況は、スレッドの最大数に達し、スレッド プールが新しく送信されたタスクの処理を続行する能力を失った場合であり、これも拒否されます。

拒否ポリシー:

  • AbortPolicy: 例外を直接スローする、デフォルトのポリシー。
  • CallerRunsPolicy: 呼び出し元のスレッドを使用してタスクを実行します。
  • DiscardOldestPolicy: ブロッキング キューの最前部のタスクを破棄し、現在のタスクを実行します。
  • DiscardPolicy: タスクを直接破棄します。もちろん、アプリケーション シナリオに従って RejectedExecutionHandler インターフェイスを実装し、処理できないタスクのログ記録や永続ストレージなどの飽和戦略をカスタマイズすることもできます。

3. スレッド処理タスクの処理

画像の説明を追加してください
画像の説明を追加してください
1. スレッド プールが corePoolSize より小さい場合、その時点でスレッド プールにアイドル状態のスレッドがあったとしても、新しく送信されたタスクはタスクを実行するための新しいスレッドを作成します。

2. スレッド プールが corePoolSize に達すると、新しく送信されたタスクは workQueue に置かれ、スレッド プールでのタスクのスケジュールと実行を待ちます。

3. workQueue がいっぱいで、maximumPoolSize が corePoolSize より大きい場合、新しく送信されたタスクは、タスクを実行するための新しいスレッドを作成します。

4. 送信されたタスクの数がmaximumPoolSizeを超えると、新しく送信されたタスクはRejectedExecutionHandlerによって処理されます。

5. スレッド プールが corePoolSize スレッドを超え、アイドル時間が keepAliveTime に達すると、アイドル状態のスレッドを閉じます。


4. スレッド拒否戦略

スレッド プール内のスレッドが使い果たされているため、新しいタスクを処理し続けることができず、同時に待機キューがいっぱいで新しいタスクを埋めることもできません。現時点では、この問題に合理的に対処するための政策メカニズムを拒否する必要があります。

JDK の組み込み拒否ポリシーは次のとおりです。

AbortPolicy : システムが正常に実行されないように例外を直接スローします。ビジネス ロジックに基づいて、送信の再試行や放棄などの戦略を選択できます。

CallerRunsPolicy : スレッド プールが閉じられていない限り、このポリシーは現在破棄されているタスクを呼び出し側スレッドで直接実行します。
タスクの損失は発生しませんが、タスクの送信速度が遅くなり、タスクの実行にバッファ時間がかかります。

DiscardOldestPolicy : 実行しようとしているタスクである最も古いリクエストを破棄し、現在のタスクを再度送信してみます。

DiscardPolicy : このポリシーは、何も処理せずに処理できないタスクをサイレントに破棄します。タスクが失われることが許容される場合、これが最良の解決策です。


5. Execuors クラスはスレッド プールを実装します

画像の説明を追加してください

  • newSingleThreadExecutor (): スレッドが 1 つだけあるスレッド プール タスクは順番に実行されるため、タスクを 1 つずつ実行するシナリオに適しています。
  • newCachedThreadPool (): スレッド プールには、同時に実行して 60 秒以内に再利用する必要があるスレッドが多数あり、負荷の軽い、短期間の非同期の小規模なプログラムやサービスを多数実行するのに適しています。
  • newFixedThreadPool (): スレッド数が固定されたスレッドプールで、タスクが実行されない場合、スレッドは永久に待機するため、長期タスクの実行に適しています。
  • newScheduledThreadPool (): 今後のタスクをスケジュールするために使用されるスレッド プール
  • newWorkStealingPool (): 最下層は forkjoin の Deque を使用します。
    画像の説明を追加してください
    上記の方法には欠点があるため、独立したタスク キューを使用すると、競合が減り、タスク処理が高速化されます。

FixedThreadPool および SingleThreadExecutor:リクエストに許可されるキューの長さはInteger.MAX_VALUE であり、OOM が発生します。

CachedThreadPool および ScheduledThreadPool:作成できるスレッドの数はInteger.MAX_VALUE であり、OOM が発生します。

手動で作成されたスレッド プールの最下層は ArrayBlockingQueue を使用して OOM を防止します。


6. スレッドプールサイズの設定

  • CPU 集中型 (n+1)

CPU 集中型とは、タスクがブロックせずに大量の計算を必要とし、CPU が常にフルスピードで実行されていることを意味します。

CPU を集中的に使用するタスクの場合、スレッドの数はできるだけ少なくする必要があります。一般的には、CPU コアの数 + 1 スレッドのスレッド プールです。

  • IO集中型(2*n)

IO 集中型のタスク スレッドが常にタスクを実行しているわけではないため、CPU * 2 など、より多くのスレッドを割り当てることができます。

次の式を使用することもできます: CPU コア数 * (1 + 平均待機時間 / 平均作業時間)。


7. スレッド プールを使用する理由は何ですか?

利点: 作成されたスレッドを再利用することで、リソースの消費が削減され、スレッドがキュー内のタスクを直接処理して応答を高速化でき、統合された監視と管理が容易になります。

1. 資源消費量の削減

スレッドの作成と破棄には一定の時間とスペースが消費されますが、スレッド プールを使用すると、作成したスレッドを再利用できます。

2. 応答速度の向上

スレッド プールはすでにスレッドを作成しているため、タスクが到着すると、スレッドの作成を待たずにすぐに実行できます。

3. スレッドの管理性の向上

スレッドは希少なリソースであり、無限に作成することはできませんが、スレッド プールは統合された割り当て、チューニング、監視に使用できます。

4. より強力な機能を提供

スレッド プールは拡張可能であるため、開発者はスレッド プールにさらに多くの機能を追加できます。たとえば、遅延スケジュールされたスレッド プール ScheduledThreadPoolExecutor を使用すると、タスクを遅延または定期的に実行できます。


8. ブロッキングキューの役割

1. 一般キューは、限られた長さのバッファであることしか保証できません。バッファ長を超えると、現在のタスクを保持できません。ブロッキング キューは、キューに追加し続けたい現在のタスクを保持できます。ブロッキング。

ブロッキング キューを使用すると、タスク キューにタスクがないときにタスクを取得するスレッドが確実にブロックされ、スレッドが待機状態になり、CPU リソースが解放されます。

ブロッキング キューには独自のブロッキング機能とウェイクアップ機能があり、追加の処理は必要ありません。タスクが実行されていない場合、スレッド プールはブロッキング キューの take メソッドを使用して一時停止し、コア スレッドの存続を維持し、コア スレッドを占有しないようにします。常に CPU リソースを確保します。

最初に最大のスレッドを作成するのではなく、最初にキューを追加するのはなぜでしょうか?

新しいスレッドを作成する場合、グローバル ロックを取得する必要がありますが、このとき他のスレッドをブロックする必要があるため、全体の効率に影響します。


9. スレッドプールでのスレッド再利用の原則

スレッド プールは、スレッドとタスクを切り離します。スレッドはスレッドであり、タスクはタスクです。Thread を通じてスレッドを作成するときに、1 つのスレッドが 1 つのタスクに対応する必要があるという以前の制限が取り除かれます。

スレッド プールでは、同じスレッドが実行のためにブロッキング キューから新しいタスクを継続的に取得できます。基本原則は、スレッド プールが Thread をカプセル化することです。新しいスレッドを作成するためにタスクが実行されるたびに Thread.start() が呼び出されるわけではありません。代わりに、各スレッドは「周期タスク」の実行を依頼されます。この「周期タスク」では、実行する必要があるタスクがあるかどうかが常にチェックされ、存在する場合は直接実行されます。タスク内の run メソッドが呼び出され、run メソッドは固定スレッドのみを使用してすべてのタスクの run メソッドが連結される共通メソッド実行とみなされます。

おすすめ

転載: blog.csdn.net/weixin_43228814/article/details/132636538