同時実行ベースのスレッド プール (スレッド プール)

序文

現在のシステム アーキテクチャの多くが高い同時実行性を必要とすることは、誰もが知っていると思います. いわゆる高い同時実行性 (High Concurrency) とは、システムが設計を通じて複数の並列要求に対応できる能力を指します. TPS. では、システムがこれらの高い同時実行能力を満たすにはどうすればよいでしょうか? 高い並行性機能を満たすことは、分散分離、読み取りと書き込みの分離、電流制限とピーク シェービング、キャッシング、およびキューだけでなく、コード記述のレベルでのマルチスレッド アプリケーションでもあり、単位時間でビジネス機能を完了することができます。システムスループットを改善するためにできるだけ早く、QPS/TPS はスループットが増加すると自然に増加します。そのため、今日は主に並行性基盤のスレッド プールについて簡単に説明します。

スレッドプールとは

スレッド プール 英語のスレッド プールは、名前が示すように、スレッド処理の形式であり、スレッドでいっぱいのプールです。タスクを処理する必要がある場合は、実行のためにスレッド プールからスレッドを直接取得します。これにより、スレッド作成のオーバーヘッドが削減され、システム オーバーヘッドに対するスレッドの作成が多すぎることによる影響が回避されます。システムの運用効率を向上させます。

スレッドプールの利点

スレッド プールを使用する利点は次のように要約できます:
1. スレッドを再利用して、頻繁なスレッド作成のオーバーヘッドを回避し、システム パフォーマンスを向上させる;
2. タイミング スケジューリング、シングル スレッド、および同時数制御機能を提供して、特定のビジネス シナリオの実現を容易にする;
3. 同時スレッド数を柔軟に制御し、リソースを可能な限り圧迫してシステム効率を向上させ、システムをブロックするスレッドが多すぎるのを回避します; 4、システムの動作リソースを監視できるスレッド監視機能を提供し
ます

スレッドプールの作成方法

ThreadPoolExecutor クラスを直接インスタンス化する

ThreadPoolExecutor クラスを直接インスタンス化し、カスタム構築パラメーターを渡す

private ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
        // 线程池核心池的大小
        1,
        // 线程池的最大线程数
        2,
        // 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间
        1,
        // 等待的时间单位
        TimeUnit.SECONDS,
        // 用来储存等待执行任务的队列
        new ArrayBlockingQueue<Runnable>(10),
        //线程工厂
        new ThreadPoolExecutor.DiscardOldestPolicy());

JUC Executor はスレッド プールを作成します

Executors 类有很多创建线程池的构造方法,如:
public static ExecutorService newSingleThreadExecutor() {
    
    
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

これは本質的に、呼び出された ThreadPoolExecutor スレッド実行クラスのコンストラクターです。

スレッドプールマイニング

Executor の簡単な紹介

Java JUC パッケージの Executors クラスは、スレッド プールを作成するためのさまざまなメソッドを提供します。
ここに画像の説明を挿入


1. Executors.newCachedThreadPool: キャッシュ可能なスレッド プールを作成します. スレッド プールのサイズが必要以上に大きくなった場合, アイドル状態のスレッドを柔軟にリサイクルできます. リサイクル可能なスレッドがない場合スレッド, 新しいスレッドを作成します
2. Executors.newFixedThreadPool: 同時スレッドの最大数を制御できる固定長のスレッド プールを作成します. 余分なスレッドはキューで待機します 3. Executors.newScheduledThreadPool:
固定長のスレッド プールを作成しますExecutors.newSingleThreadExecutor: シングルスレッドスレッド
プールを作成し、一意のワーカー スレッドを使用してタスクを実行し、すべてのタスクが指定された順序 (先入れ先出しまたは優先順位) で実行されるようにします 5 . Executors.newSingleThreadScheduledExecutor
: タイミングと定期的なタスク実行をサポートするシングルスレッド スレッド プールを作成します
6. Executors.newWorkStealingPool: 並列レベルでワークスティーリング スレッド プールを作成します

ThreadPoolExecutor コア クラス

上記は、スレッドプールを作成するためのいくつかの一般的な方法を説明しており、JUC の Executor はすでにこれらの方法を提供しています。では、これらの一般的に使用されるメソッドは、どのようにしてスレッド プールを作成するのでしょうか? まず、ソース コードを表示する作成方法を確認して選択しましょう。

//Executors.newFixedThreadPool 创建定长线程池方式
public static ExecutorService newFixedThreadPool(int nThreads) {
    
    
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

ソース コードを表示すると、固定長スレッド プールを作成する静的メソッドがスレッド プール実行クラス ThreadPoolExecutor を内部的にインスタンス化し、ThreadPoolExecutor クラスに再び入ってソース コードを表示することがわかります。

//ThreadPoolExecutor 线程池执行类的一个有参数构造方法
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    
    
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}
// ThreadPoolExecutor 线程池执行类内部构造方法,
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;
}

ソース コードを見ると、スレッド プールの作成は、基本的に ThreadPoolExecutor スレッド実行クラスをインスタンス化し、複数の構築パラメーターを渡すことであることがわかります。ThreadPoolExecutor スレッド実行クラスは、現時点ではこれらの構成パラメーターをこれらの変数に割り当てるだけであり、後続のスレッド プールの実行中にスレッドの作成、破棄、および呼び出しに備えます。

スレッド プールの使用シナリオに従って、execute() 実行メソッドを選択してソース コードを解釈します。

public void execute(Runnable command) {
    
    
    if (command == null)
        throw new NullPointerException();
    /*
     * Proceed in 3 steps:
     *
     * 1. If fewer than corePoolSize threads are running, try to
     * start a new thread with the given command as its first
     * task.  The call to addWorker atomically checks runState and
     * workerCount, and so prevents false alarms that would add
     * threads when it shouldn't, by returning false.
     *
     * 2. If a task can be successfully queued, then we still need
     * to double-check whether we should have added a thread
     * (because existing ones died since last checking) or that
     * the pool shut down since entry into this method. So we
     * recheck state and if necessary roll back the enqueuing if
     * stopped, or start a new thread if there are none.
     *
     * 3. If we cannot queue task, then we try to add a new
     * thread.  If it fails, we know we are shut down or saturated
     * and so reject the task.
     */
    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
    
    
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    if (isRunning(c) && workQueue.offer(command)) {
    
    
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    else if (!addWorker(command, false))
        reject(command);
}

ソース コードに示すように、execute() メソッドは主に次の 3 つのことを行うことがわかります。
1. 実行中のスレッドの数が corePoolSize よりも少ない場合、コア スレッドは新しいスレッドを開始してタスクを実行しようとします、addWorker メソッドの runState と workerCount をチェックして、アラームを回避します。
2. タスクを正常にキューに入れることができる場合は、新しいスレッドを作成する必要があるかどうかも確認します。スレッドが終了したか、最後のチェック後にスレッド プールが閉じられた可能性があるため、この時点でタスクを実行するために、ロールバックしてキューに入れ、スレッドを再作成する必要があります。
3. タスクをキューに入れることができない場合は、新しいスレッドを追加してみてください。新しいスレッドが失敗した場合は、スレッドが閉じられたか飽和状態になったことを認識し、拒否ポリシーがユーザーに表示されます。

もちろん、タスクをサブミットする submit()、スレッド プールを閉じる shutdown()、スレッド プールをすぐに閉じる shutdownNow() など、他のメソッドもあります。これらのソース コードも比較的単純で、自分で読むことができます。

ThreadPoolExecutor クラス構築パラメーターの意味

corePoolSize: コア スレッドの数
maximumPoolSize: スレッドの最大数
keepAliveTime: スレッドの数が corePoolSize コア スレッドの数よりも多く、これらのスレッドがアイドル状態の場合、アイドル スレッドの生存時間、この生存を超えたスレッド時間は破棄されます
TimeUnit: スレッドの時間単位BlockingQueue: スレッド ブロック キュー。状況に応じて
お気に入りのブロック キューを渡すことができます。 ThreadFactory: スレッド作成ファクトリRejectedExecutionHandler: スレッド プールの拒否戦略。渡された構成パラメーターに対して、現在一般的に使用されている拒否戦略は次のとおりです: 1. デフォルト ポリシーでもある例外を直接スローし、実装クラスは AbortPolicy です; 2. 呼び出し元が配置されているスレッドを使用してタスクを実行します。実装クラスは CallerRunsPolicy; 3. キュー内の最前列のタスクを破棄し、現在のタスクを実行 , 実装クラスは DiscardOldestPolicy; 4. 現在のタスクを直接破棄し、実装クラスは DiscardPolicy.





スレッドプールの操作規則

スレッド プール実行タスクの実行規則は次のとおりです:
1. 実行中のスレッドの数が corePoolSize コア スレッドの数よりも少ない場合、コア スレッドがアイドル状態であるかどうかに関係なく、実行のために新しいスレッドが作成されます
。 BlockingQueue ブロッキング キューがいっぱいの場合、実行用の新しいスレッドを作成します。そうでない場合は、それをブロッキング キューに入れ、アイドル スレッドが実行されるのを待ちます 3. 実行中のスレッドの数が maximumPoolSize よりも大きく、BlockingQueue ブロッキング キュー
が完全な場合、RejectedExecutionHandler 例外戦略が実行され、デフォルトでは直接例外 4 がスローされます。
実行中のスレッドの数が corePoolSize コア スレッドの数よりも多く、アイドル スレッドがある場合、アイドル スレッドは、 keepAliveTime 生存時間は、スレッド数が corePoolSize コア スレッド数と等しくなるまで期限切れになります

スレッドセット数

1. CPU 集中型タスクの場合、CPU をできるだけ圧迫する必要があります. 一般的に、スレッド数は nCPU + 1 が推奨されます. 2. IO 集中型タスクの場合、スレッド数は一般的
に2nCPU

エピローグ

The flexible use of the thread pool is a strong tool for multi-threaded development to meet high concurrency scenarios. 高同時実行性の機能サービスを開発するときは、マルチスレッドを合理的に使用する必要があります。特に、適切なスレッド プールを作成できるように、ThreadPoolExecutor コア クラスのソース コード設計を理解する必要があります。水はボートを運び、それを転覆させることができます. 優れたマルチスレッド アプリケーションはシステムのスループットを向上させることができます, マルチスレッドの誤用も異常な状況につながる可能性があります.

おすすめ

転載: blog.csdn.net/weixin_39970883/article/details/129585479