スレッドプールのソースコードの解釈と原理

序文

老プログラマー Lao Wang Lao
Wang は北京に 10 年以上漂流しているプログラマーで、残業するほどの年齢にもかかわらず昇進できません。彼は入浴業界を選び、入浴センター、そう、普通の入浴センターを開設しました。以前北京にいた時、よく通っていた銭湯が「清華潭」と呼ばれていたため、考えた末に自分の沐浴場を「糸潭」と名付けた。

スレッド プール 入浴センター
スレッド プールがオープンした後、ラオ ワン氏は、ペディキュアを希望する顧客がいることに気づき、ペディキュア技術者を雇用し、収入を増やすために追加のビジネスを追加しました。ペディキュアの顧客数が増加するにつれて、より多くの収入を得るためにさらに 4 人のペディキュア技術者が採用されました。
しばらくすると、入浴センターの業績はますます良くなり、ペディキュアをする顧客も増えてきました。しかし、Lao Wang さんは、自分の店にはすでに 5 人のペディキュア技術者がおり、採用人数が多すぎるため、これ以上の賃金を支払う余裕がないことに気付きました。ペディキュア技術者が忙しすぎる場合はどうすればよいですか? ラオ・ワンは賢い人だったので、すぐに解決策を思いつきました。顧客を一列に並べ、ペディキュア技術者の誰かが終わって空いた場合は、列に並んでいる別の顧客に作業を続けるよう依頼するというものでした。

週末は賑わう 週末
は入浴センターを訪れるお客様が通常の数倍で、ペディキュアを希望されるお客様の行列は長すぎて、お客様はすでに焦っています。Lao Wang はすぐに反応し、他の入浴センターからチームで顧客のペディキュアを担当するペディキュア技術者 5 人を緊急採用し、行列に並ぶ顧客の数を大幅に減らしました。
しかし、時々、急遽採用した技術者を使用するほど業務が過熱し、顧客の待ち時間も非常に長くなることがあります。新しい顧客が来たとき、老王は顧客に笑顔でこう言うことしかできません。「次も来てください」次回、次回もあなたにぴったりの技術者を見つけてください。」と言って顧客をシャットアウトしました。
週末の後、店は怠け者をサポートできなくなったため、Lao Wang は緊急採用した技術者を全員解雇した。

ラオ・ワンの経営方法
ラオ・ワンのビジネスは順調で、間もなく支店を開設し、株式公開のための資金を集め、人生の頂点に達するでしょう。非常に成功しているので、彼のビジネス手法を振り返ってみましょう:
———————————————

1. スレッドプールの概要

1.1 スレッドの基本概念

スレッドのライフサイクルは次のとおりです。

ここに画像の説明を挿入

新建:java.lang.Thread.State.NEW
public static void thread_state_NEW(){
    
    
        Thread thread = new Thread();
        System.out.println(thread.getState());
    }
就绪:java.lang.Thread.State.RUNNABLE
public static void thread_state_RUNNABLE(){
    
    
        Thread thread = new Thread();
        thread.start();
        System.out.println(thread.getState());
    }
超时等待:java.lang.Thread.State#TIMED_WAITING
public static void thread_state_SLEEP(){
    
    
        Thread thread3 = new Thread(() -> {
    
    
            try {
    
    
                Thread.sleep(10000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            } });
        thread3.start();
        Thread.sleep(500);
        System.out.println(thread3.getState());
    }
等待:java.lang.Thread.State.WAITING
public static void thread_state_WAITING(){
    
    
        Thread thread2 = new Thread(new Runnable() {
    
    
            public void run() {
    
    
                LockSupport.park();
            }
        });
        thread2.start();
        Thread.sleep(500);
        System.out.println(thread2.getState());
        LockSupport.unpark(thread2);
    }
阻塞:java.lang.Thread.State.BLOCKED
    public static void thread_state_BLOCKED(){
    
    
        final byte[] lock = new byte[0];
        Thread thread1 = new Thread(() -> {
    
    
            synchronized (lock){
    
    
                try {
    
    
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                } }
        });
        thread1.start();
        Thread thread2 = new Thread(() -> {
    
    
            synchronized (lock){
    
    
            } });
        thread2.start();
        Thread.sleep(1000);
 
        System.out.println(thread1.getState());
        System.out.println(thread2.getState());
    }
销亡:java.lang.Thread.State.TERMINATED

public static void thread_state_TERMINATED(){
    
    
        Thread thread = new Thread();
        thread.start();
        Thread.sleep(1000);
        System.out.println(thread.getState());
    }

1.2 スレッドプールの基本概念

1.2.1 スレッドプールを使用する理由

また、プロジェクト内でスレッドプールを使用する場合の注意点もありますので、詳しくは『Java開発マニュアル 泰山編』を参照してください: 【必須】スレッドリソースはスレッドプール経由で提供する必要があり、明示的に
スレッドを作成することはできませんアプリケーションで。
説明: スレッド プールの利点は、スレッドの作成と破棄にかかる時間とシステム リソースのオーバーヘッドを削減し、リソース不足の問題を解決することです。スレッド プールを使用しない場合、システムが同じタイプのスレッドを多数作成し、メモリの消費または「オーバースイッチング」が発生する可能性があります。
[必須] スレッド プールの作成に Executor を使用することはできませんが、ThreadPoolExecutor を使用することは許可されません。この処理方法を使用すると、学生はスレッド プールの実行ルールをより明確にし、リソース枯渇のリスクを回避できます。

Executor によって返されるスレッド プール オブジェクトの短所は次のとおりです。
FixedThreadPool および SingleThreadPool:
許可されるリクエスト キューの長さは Integer.MAX_VALUE であり、大量のリクエストが蓄積され、OOM が発生する可能性があります。
CachedThreadPool:
作成できるスレッドの数は Integer.MAX_VALUE です。これにより、多数のスレッドが作成され、OOM が発生する可能性があります。

1.2.2 原則

スレッド プール (ThreadPool): スレッド プールは、スレッドを格納するためのバッファ プールを作成します。実行後、スレッドは停止せず、再びスレッド プールに戻ってアイドル状態になり、次のタスクが来るのを待ちます。スレッド プールを手動よりも有効にします。スレッドを作成すると利点が多く、同時
実行性の高いシナリオでよく使用されます。したがって、マルチスレッドを使用してコード効率を最適化すると、スレッド プールを使用すると、手動でスレッドを作成するよりも多くの利点があります。
システム リソースの消費が削減され、既存のスレッドを再利用することでスレッドの作成と破棄による消費が削減されます。

システムの応答速度の向上 タスクが到着した際、既存のスレッドを再利用することで、新規スレッドの作成を待たずにすぐに実行できるため、同時スレッド数の制御に便利です

スレッドが無制限に作成されると、メモリ使用量が過剰になり、CPU がスレッドを切り替える時間コストを節約するためにOOM が発生する可能性があるため(現在の実行スレッド サイトを維持し、実行スレッド サイトを復元する必要があります)
、より強力な機能を提供します。遅延タイミング スレッド プール。Timer と ScheduledThreadPoolExecutor の
共通スレッド プール構造 (UML)

ここに画像の説明を挿入

Executor
: 最上位インターフェイス Executor は、タスクの送信とタスクの実行を分離するというアイデアを提供します。
ExecutorService は、
タスクを実行する機能を拡張し、1 つまたは非同期タスクのバッチに対して Future を生成できるメソッドを補完し、
スレッド プールの操作の停止など、スレッド プールを管理および制御するためのメソッドを提供します。
AbstractExecutorService
の上位層の抽象クラスは、タスクを実行するプロセスを直列に接続し、下位層の実装がタスクを実行する 1 つのメソッドのみに集中する必要があるようにします。ThreadPoolExecutor は、最も一般的に使用されるスレッド プール
です
。 、独自のライフサイクルを維持し、一方でスレッドとタスクを同時に管理するため、この 2 つをうまく組み合わせて並列タスクを実行します。

1.2.3 スレッドプールのステータス

ここに画像の説明を挿入

実行中: 新しいタスクを受け入れ、キューに入れられたタスクを処理します。
シャットダウン: 新しいタスクを受け入れませんが、キューに入れられたタスクを処理します。
STOP: 新しいタスクを受け入れず、キューに入れられたタスクを処理せず、進行中のタスクを中断します。
TIDYING: すべてのタスクが終了しており、workerCount は 0 であり、TIDYING 状態に移行するスレッドは、terminated() フック メソッドを実行します。
TERMINATED:terminated() が完了しました。
上記はスレッド プールの 5 つの状態ですが、これら 5 つの状態は何によって記録されるのでしょうか? 以下の詳細をマークしてください。

1.2.4 実行プロセス

ここに画像の説明を挿入

仮想シナリオ:
スレッド プールを作成し、無限ループでタスクを追加し、デバッグしてワーク数とキューの数の増加パターンを確認し、一定時間待機した後、
ワーク数がコアに戻るかどうかを確認します

最初に結論を添付します: タスクを追加
します。スレッド プール内のスレッド数が coreSize に達しない場合は、新しいスレッドを直接作成して実行します。コアに達したら、キューに入れます。キューがいっぱい
です
。 maxSize に達していない場合は、スレッドの作成を続行します
。maxSize
に達すると、スレッドは拒否ポリシーに従って拒否されます。リリース、coreSize まで減少

2.動作原理

パラメータの紹介
まず、ThreadPoolExecutor のコンストラクタについて理解しましょう
ソース コードから、ThreadPoolExecutor のコンストラクタには、corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler の 7 つのパラメータがあることがわかります。以下では、これら 7 つのパラメータを 1 つずつ説明します。

ここに画像の説明を挿入

2.1 corePoolSize コアスレッドの数:

スレッド プールには最小数のスレッドが維持され、これらのスレッドがアイドル状態であっても、allowCoreThreadTimeOut が設定されない限り破棄されません。ここでのスレッドの最小数は corePoolSize です。タスクがスレッド プールに送信されると、まず現在のスレッド数が corePoolSize に達したかどうかがチェックされ、達していない場合は、タスクを処理するために新しいスレッドが作成されます。

2.2 minimumPoolSize スレッド プール内のスレッドの最大数:

現在のスレッド数が corePoolSize に達した後も、タスクがスレッド プールにサブミットされ続けると、タスクはワーク キューにキャッシュされます (後述)。キューもいっぱいの場合は、これを処理するために新しいスレッドが作成されます。スレッド プールは新しいスレッドを無期限に作成するわけではなく、スレッドの最大数には制限があり、maximumPoolSize で指定されます。

2.3 keepAliveTime アイドル スレッドの生存時間:

  一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指定时间后,这个空闲线程会被销毁,这里的指定时间由keepAliveTime来设定

2.4 単位アイドルスレッド生存時間単位:

keepAliveTime の測定単位、一般的に使用される SECONDS (秒) MILLISECONDS (ミリ秒)

2.5 workQueue ワークキュー:

タスクキュー。実行待ちのタスクを転送および保存するためのブロックキュー。corePoolSize の初期化が完了すると、次のタスクがキューに直接格納され、スレッドが回転して getTask() メソッドを通じてタスクを取得します。一般的なキューの設定は次のとおりです。

①ArrayBlockingQueue 配列ブロッキングキュー:
FIFOでソートされた配列ベースの有限ブロッキングキュー。新しいタスクが到着すると、それらはキューの最後に配置され、境界付き配列によりリソースの枯渇を防ぐことができます。スレッド プール内のスレッドの数が corePoolSize に達し、新しいタスクが到着すると、そのタスクはキューの最後に配置され、スケジュールされるのを待ちます。キューがすでにいっぱいの場合は、新しいスレッドが作成され、スレッドの数が maxPoolSize に達すると、拒否戦略が実行されます。

②※LinkedBlockingQuene リンクリストブロッキングキュー(注:長さ指定可能):
FIFOに従ってソートされた、リンクリストに基づく無制限のブロッキングキュー(デフォルトの最大容量はInterger.MAX、長さは指定可能)。キューのほぼ無制限の性質により、スレッド プール内のスレッド数が corePoolSize に達すると、新しいタスクは常にキューに格納され、基本的に maxPoolSize になるまで新しいスレッドは作成されません (この数に達することは困難です)。 Interger.MAX ) なので、このワーク キューを使用する場合、パラメータ maxPoolSize は実際には効果がありません。

③SynchronousQuene 同期キュー:
タスクをキャッシュしないブロッキングキューで、プロデューサはタスクを投入し、コンシューマがタスクを取り出すまで待たなければなりません。つまり、新しいタスクが到着すると、キャッシュされませんが、タスクの実行が直接スケジュールされます。使用可能なスレッドがない場合は、新しいスレッドが作成されます。スレッドの数が maxPoolSize に達すると、拒否戦略が実行されます。

④PriorityBlockingQueue 優先ブロッキングキュー:
優先度を持つ無制限のブロッキングキュー。優先度はパラメータ Comparator によって実現されます。

2.6、threadFactory スレッド ファクトリ:

新しいスレッドの作成時に使用されるファクトリは、スレッド名、デーモン スレッドかどうかなどを設定するために使用できます。

2.7、ハンドラー拒否戦略:

ワークキュー内のタスクが上限に達し、スレッド プール内のスレッド数も上限に達した場合に、新しいタスクが投入された場合の対処方法。ここでの拒否戦略は、この問題を解決するためのもので、jdk では 4 つの拒否戦略を提供しています:
①CallerRunsPolicy
この戦略では、拒否されたタスクの run メソッドが呼び出し側スレッドで直接実行されます。
②AbortPolicy
このポリシーでは、タスクは直接破棄され、RejectedExecutionException がスローされます。
ps: ThreadPoolTask​​Executor は、このポリシーの下でデフォルトで
③DiscardPolicyに
なり、タスクを直接破棄し、何もしません。
④DiscardOldestPolicy
このポリシーでは、キューに入った最も古いタスクを破棄し、拒否されたタスクをキューに入れようとします。

3 ソースコード分析

上記スレッドプールの基本情報を紹介したら、次はソースコード解析を開始します。まずはソースコードの基本的な概念を見てみましょう。

3.1 基本概念:CTL

「ctl」とは何ですか?
ctl は、2 つの概念フィールドをパックするアトミック整数です。
1) workerCount: スレッドの有効数を示します;
2) runState: RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED およびその他の状態を含む、スレッド プールの実行状態を示します。

int 型は 32 ビットで、次の図に示すように、ctl の下位 29 ビットは workerCount を表すために使用され、上位 3 ビットは runState を表すために使用されます。

ここに画像の説明を挿入

ソースコードの紹介:

/**
     * 主池控制状态ctl是包含两个概念字段的原子整数: workerCount:指有效的线程数量;
     * runState:指运行状态,运行,关闭等。为了将workerCount和runState用1个int来表示,
     * 我们限制workerCount范围为(2 ^ 29) - 1,即用int的低29位用来表示workerCount,
     * 用int的高3位用来表示runState,这样workerCount和runState刚好用int可以完整表示。
     */
    // 初始化时有效的线程数为0, 此时ctl为: 1010 0000 0000 0000 0000 0000 0000 0000
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    // 高3位用来表示运行状态,此值用于运行状态向左移动的位数,即29位
    private static final int COUNT_BITS = Integer.SIZE - 3;
    // 线程数容量,低29位表示有效的线程数, 0001 1111 1111 1111 1111 1111 1111 1111
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
 
    /**
     * 大小关系:RUNNING < SHUTDOWN < STOP < TIDYING < TERMINATED,
     * 源码中频繁使用大小关系来作为条件判断。
     * 1110 0000 0000 0000 0000 0000 0000 0000 运行
     * 0000 0000 0000 0000 0000 0000 0000 0000 关闭
     * 0010 0000 0000 0000 0000 0000 0000 0000 停止
     * 0100 0000 0000 0000 0000 0000 0000 0000 整理
     * 0110 0000 0000 0000 0000 0000 0000 0000 终止
     */
    private static final int RUNNING    = -1 << COUNT_BITS; // 运行
    private static final int SHUTDOWN   =  0 << COUNT_BITS; // 关闭
    private static final int STOP       =  1 << COUNT_BITS; // 停止
    private static final int TIDYING    =  2 << COUNT_BITS; // 整理
    private static final int TERMINATED =  3 << COUNT_BITS; // 终止

実行状態の取得:

/**
 * 得到运行状态:入参c为ctl的值,~CAPACITY高3位为1低29位全为0,
 * 因此运算结果为ctl的高3位, 也就是运行状态
 */
private static int runStateOf(int c)     {
    
     return c & ~CAPACITY; }

workCount の取得:

/**
     * 得到有效的线程数:入参c为ctl的值, CAPACITY高3为为0,
     * 低29位全为1, 因此运算结果为ctl的低29位, 也就是有效的线程数
     */
    private static int workerCountOf(int c)  {
    
     return c & CAPACITY; }

3.2 このような CTL 設計の利点は何ですか?

ctl の設計の主な利点は、runState と workerCount の操作がアトミックな操作にカプセル化されることです。
runState と workerCount は、スレッド プールの通常の動作において最も重要な 2 つの属性であり、スレッド プールが特定の瞬間に何をすべきかは、これら 2 つの属性の値によって決まります。

したがって、クエリであっても変更であっても、これら 2 つの属性に対する操作が「同じ瞬間」に属すること、つまりアトミック操作であることを確認する必要があります。そうしないと混乱が発生します。2 つの変数を使用して個別に格納する場合、アトミック性を確保するために追加のロック操作が必要になり、明らかに追加のオーバーヘッドが発生しますが、これら 2 つの変数を AtomicInteger にカプセル化しても追加のロック オーバーヘッドは発生しません。ロック オーバーヘッドはなく、runState と workerCount は単純なビット演算を使用して個別に取得されます。

3.3 ソースコードのデバッグシナリオ

さらに上記の仮説シナリオ:
スレッド プールを作成し、無限ループでタスクを追加し、作業とキューの数の増加の法則を確認するためにデバッグします。
コア スレッド 3、境界付きキュー 2、最大スレッド 5
待機時間 20 秒、カスタム拒否戦略
一定時間待機した後、作業数を確認します コア
タスクシナリオにフォール
バックするかどうか スレッドプールがオープンしました 最初の 3 日間で大きな報酬がありました 訪問者の速度が消費速度を上回りました観察は徐々に増加し、
8 日目にはスレッド プールが開始され、タスクの量は減少し、生成の速度よりも消費の速度が速くなりました。緩やかな減少傾向を観察

/**
     * desc : 回落场景
     */
    @SneakyThrows
    private static void test_threadPoolExecutor_down_core() {
    
    
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                3,
                5,
                20,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(2),
                new RejectedExecutionHandler() {
    
    
                    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
    
    
                        System.out.println("爆满了,择日再来啊!!!!!拒绝任务~~~~~~~~~~~~~~~");
                    }
                });
 
        // 开业前七天高峰,瞬间打满
        for (int i = 0; i < 100; i++) {
    
    
 
            int finalI = i + 1;
            executor.execute(() -> {
    
    
                try {
    
    
                    System.out.println("~~~~~~~~~~~~~~~~~~~~~~来活了,第" + finalI + "位客人~~~~~~~~~~~~~~~~~~~~~~");
                    System.out.println("当前排队任务数: " + executor.getQueue().size() + "  当前线程数量: " + 
                    executor.getPoolSize());
                    Thread.sleep(10 * 1000);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            });
            if (i >= 7) {
    
    
                if (i == 8) {
    
    
                    System.out.println("线程任务高峰期已过 | 分界线!!!!!!!!!!");
                }
                // 此时任务产生的速率高于线程执行速度(线程有富余)
                Thread.sleep(15 * 1000);
            } else {
    
    
                Thread.sleep(1L * 1000);
            }
        }
    }

結果は次のとおりです。
スレッドは徐々に 3 に増加し、次にキューが 2 に増加し、次にスレッドが 5 に増加します。7番目のミッションが来たら頂上に登る
ここに画像の説明を挿入

後続のタスクの生成が遅くなり、キューのタスクが 2 ~ > 0 ずつ減り、その後、スレッドの数が 5 ~ > 3 と徐々に減ります。

ここに画像の説明を挿入

上記の事例を通じてスレッドプールが動的に変化する過程を観察することができ、その原因をソースコードの観点から解析していきます。[追記: ソースコードを広い視点から深い視点まで追跡してください]

3.4 ソースコードのデバッグプロセス

public void execute(Runnable command) {
    
    
        // 防御性容错
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        // case1 -> worker数量小于核心线程数,addWork
        if (workerCountOf(c) < corePoolSize) {
    
    
            // 添加worker - core
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        // case2 -> 如果线程池还在运行态,offer到队列
        if (isRunning(c) && workQueue.offer(command)) {
    
    
            //再检查一下状态
            int recheck = ctl.get();
            //如果线程池已经终止,直接移除任务,不再响应
            if (! isRunning(recheck) && remove(command))
                reject(command);
            //否则,如果没有线程干活的话,创建一个空work,该work会从队列获取任务去执行
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        // case3 -> 队列也满,继续调addWork,但是注意,core=false,开启到maxSize的大门
        else if (!addWorker(command, false)) {
    
    
            // case4 -> 超出max的话,addWork会返回false,进入reject
            reject(command);
        }
    }

次に、2 つの入力パラメーター (タスク、コア スレッドかどうか) を提供する addWork メソッドに入ります。内部ロジックは次のとおりです。

/**
     * desc : 线程创建过程
     */
    private boolean addWorker(Runnable firstTask, boolean core) {
    
    
        // 第一步,先是ctl-wc 通过CAS + 1
        retry:
        for (;;) {
    
    
            int c = ctl.get();
            int rs = runStateOf(c);
 
            // 判断线程池状态是否是可运行态(停止及之后 直接falsee)
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;
 
            for (;;) {
    
    
                // 获取运行中线程数量,判断是否能增加
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                // 满足条件,此时ctl-wc CAS原子性增加,正常break
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                // 增加失败,判断线程池状态决定内循环 or 外循环(结束)
                c = ctl.get();
                if (runStateOf(c) != rs)
                    continue retry;
            }
        }
 
        // 第二步,创建新work放入线程集合works(一个HashSet)
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
    
    
            // 符合条件,创建新的work并包装task
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
    
    
                // 加锁 - 避免workers的线程安全问题
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
    
    
                    // 再次校验运行状态,防止关闭
                    int rs = runStateOf(ctl.get());
 
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
    
    
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        // 添加打工人
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
    
    
                    mainLock.unlock();
                }
                if (workerAdded) {
    
    
                    // 添加成功,启动线程
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
    
    
            // 添加失败,减ctl,集合内移除
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

ここで、ワーカーのターゲットである Worker を強調して宣言します。

private final class Worker extends AbstractQueuedSynchronizer
     implements Runnable{
    
    
         /** Thread this worker is running in.  Null if factory fails. */
        final Thread thread;
        /** Initial task to run.  Possibly null. */
        Runnable firstTask;
}

Worker は、Runnable インターフェイスを実装し、スレッド thread と初期化されたタスク firstTask を保持するワーカー スレッドです。thread は、コンストラクターが呼び出されたときに ThreadFactory によって作成されるスレッドで、タスクの実行に使用できます。firstTask は、渡された最初のタスク (null または null にすることができます) を保存するためにそれを使用します。この値が null 以外の場合、スレッドは起動開始時にすぐにこのタスクを実行します。これは、コア スレッドが作成されたときの状況に対応します。この値が null の場合、スレッドを実行するにはスレッドを作成する必要があります。タスク リスト (workQueue) 内のタスク タスク、つまり非コア スレッドの作成。

ワーカーはAQSを継承し、AQSを利用して排他ロックの機能を実現します。ReentrantLock は使用されませんが、スレッドの現在の実行状態を反映する非リエントラント機能を実現するために AQS が使用されます。スレッドをリサイクルするために使用されます。
ここに画像の説明を挿入

引き続き Worker クラスを追跡し、その run メソッドを見てみましょう。

//在worker执行runWorker()的时候,不停循环,先查看自己有没有携带Task,如果有,执行
   while (task != null || (task = getTask()) != null)
 
 
//如果没有绑定,会调用getTask,从队列获取任务
    private Runnable getTask() {
    
    
        boolean timedOut = false; // Did the last poll() time out?
        // 自旋获取任务
        for (;;) {
    
    
            int c = ctl.get();
            int rs = runStateOf(c);
 
            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
    
    
                decrementWorkerCount();
                return null;
            }
 
            int wc = workerCountOf(c);
 
            // 判断是不是要超时处理,重点!!!决定了当前线程要不要被释放
            // 首次进来 allowCoreThreadTimeOut = false 主要看 wc > corePoolSize
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
            //线程数超出max,并且上次循环中poll等待超时了,那么说明该线程已终止 //将线程队列数量原子性减
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
    
    
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }
            try {
    
    
                // 重点!!!
                // 如果线程可被释放,那就poll,释放的时间为:keepAliveTime
                // 否则,线程是不会被释放的,take一直被阻塞在这里,直到来了新任务继续工作
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                //到这里说明可被释放的线程等待超时,已经销毁,设置该标记,下次循环将线程数减少
                timedOut = true;
            } catch (InterruptedException retry) {
    
    
                timedOut = false;
            }
        }
    }

最後に、ワーカーの終了 (スレッドの解放)

    private void processWorkerExit(Worker w, boolean completedAbruptly) {
    
    
 
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
    
    
            // 统计总执行任务数 && 释放worker
            completedTaskCount += w.completedTasks;
            workers.remove(w);
        } finally {
    
    
            mainLock.unlock();
        }
        // 中断线程
        tryTerminate();
}

プロセスのレビューを完了する:
ここに画像の説明を挿入

3.5 注意点

スレッド プールはコア スレッドが破棄されないことをどのように保証しますか?
keepAliveTime 後に非コア スレッドはどのようにして停止しますか?
コア スレッドの数が corePoolSize より少なく、アイドル状態のスレッドがある場合、この時点でタスクを追加すると、スレッドまたは既存の実行が作成されます。
コア スレッドと非コア スレッドの違いは何ですか?
スレッド プールと非コア スレッドで使用されるロックは何ですか?なぜ?

おすすめ

転載: blog.csdn.net/chuige2013/article/details/131119377