8. エグゼキュータスレッドプールの原則と同時プログラミングのソースコード解釈

スレッドプール

「スレッドプール」は、その名のとおりスレッドキャッシュです。スレッドは希少なリソースです。無制限に作成すると、システムリソースを消費するだけでなく、システムの安定性も低下します。そのため、Java はスレッドを提供します。スレッドを均一に割り当ててディスパッチするためのプール。優れた監視機能

スレッドプールの概要

Web 開発では、サーバーがリクエストを受け入れて処理する必要があるため、処理するリクエストにスレッドが割り当てられます。リクエストごとに新しいスレッドが作成される場合、実装は非常に簡単ですが、問題があります。同時
リクエストの数が非常に多くても、各スレッドの実行時間が非常に短い場合、スレッドの作成と破棄が頻繁に発生します。 . システムの効率が大幅に低下します。サーバーは、実際のユーザー要求を処理するよりも、要求ごとに新しいスレッドの作成やスレッドの破棄に多くの時間を費やし、より多くのシステム リソースを消費する場合があります。
それでは、タスクを破棄せずに実行し、他のタスクの実行を継続する方法はあるのでしょうか?
これがスレッド プールの目的です。スレッド プールは、スレッド存続期間のオーバーヘッドとリソース枯渇の問題に対する解決策を提供します。複数のタスクにスレッドを再利用することで、スレッド作成のオーバーヘッドが複数のタスクにわたって償却されます。

スレッド プールをいつ使用するか?

  • 単一タスクの処理時間は比較的短い
  • 処理すべきタスクの数が多い

スレッド プールを使用する利点:

  • 既存のスレッドを再利用し、スレッドの作成と削除のオーバーヘッドを削減し、パフォーマンスを向上させます。
  • 応答性を向上させます。タスクが到着すると、スレッドの作成を待たずにすぐにタスクを実行できます。
  • スレッドの管理性を向上させます。スレッドは希少なリソースです。無制限に作成すると、システム リソースを消費するだけでなく、システムの安定性も低下します。スレッド プールを使用すると、一元的な割り当て、チューニング、監視が可能になります。

スレッドの実装

実行可能、スレッド、呼び出し可能

// Runnable Thread
public interface Runnable {
    
    
    // run
    public abstract void run();
}
public interface Callable<V> {
    
    
	// 相当于run方法有返回值的call方法
    V call() throws Exception;
}

エグゼキューターフレームワーク

Executor インターフェイスは、スレッド プール フレームワークの最も基本的な部分であり、 Runnable を実行するための実行メソッドを定義します

次の図は、その継承と実装を示しています。
ここに画像の説明を挿入
図から、Executor の下に重要なサブインターフェイス ExecutorService があることがわかります。これは、スレッド プール
1 の特定の動作を定義します。execute(Runnable command): のタスクを実行します。 Ruannable タイプ、
2、submit( task): Callable または Runnable タスクを送信するために使用でき、このタスクを表す Future オブジェクトを返します。
3、shutdown(): 送信されたタスクが完了した後にサービスを閉じ、新しいタスクを受け入れなくなります。 ,
4、shutdownNow(): 進行中のタスクをすべて停止します。タスクを実行してサービスを閉じます。
5. isTerminated(): すべてのタスクが完了したかどうかをテストします。
6. isShutdown(): ExecutorService がシャットダウンされているかどうかをテストします。

スレッドプールのキー属性

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING,0));
private static final int COUNT_BITS = Integer.SIZE ­ 3;
private static final int CAPACITY   = (1 << COUNT_BITS) ­ 1;

ctl は、スレッド プールの実行ステータスとスレッド プール内の有効なスレッドの数を制御するフィールドで、スレッド プールの実行ステータス (runState) とスレッド内の有効なスレッドの数の 2 つの情報が含まれます。 pool (workerCount). ここで、保存には Integer 型が使用され、上位 3 ビットは runState を保存し、下位 29 ビットは workerCount を保存することがわかります。COUNT_BITS は 29、CAPACITY は 1 左シフト 29 ビットマイナス 1 (29 1s) です。この定数は、workerCount の上限 (約 5 億) を表します。

CTL関連のメソッド

private static int runStateOf(int c)     {
    
     return c & ~CAPACITY; }
private static int workerCountOf(int c)  {
    
     return c & CAPACITY; }
private static int ctlOf(int rs, int wc) {
    
     return rs | wc; }

runStateOf: 実行状態を取得します
。workerCountOf: アクティブなスレッドの数を取得します。ctlOf
: 実行状態の値とアクティブなスレッドの数を取得します。

スレッド プール
ここに画像の説明を挿入
1 には 5 つの状態があります。 RUNNING
(1) 状態の説明: スレッド プールが RUNNING 状態にある場合、新しいタスクを受信し、追加されたタスクを処理できます。
(2) 状態の切り替え: スレッドプールの初期化状態は RUNNING です。つまり、スレッド プールが作成されると、スレッド プールは RUNNING 状態になり、スレッド プール内のタスクの数は 0 になります。

2. SHUTDOWN
(1) 状態の説明: スレッド プールが SHUTDOWN 状態にあるとき、スレッド プールは新しいタスクを受け取りませんが、追加されたタスクを処理できます。
(2) 状態の切り替え: スレッドプールの shutdown() インターフェースが呼び出されると、スレッドプールは RUNNING -> SHUTDOWN に切り替わります。

3. STOP
(1) 状態の説明: スレッド プールが STOP 状態にあるとき、スレッド プールは新しいタスクを受信せず、追加されたタスクを処理せず、処理中のタスクを中断します。
(2) 状態切り替え: スレッドプールの shutdownNow() インターフェースが呼び出されると、スレッドプールは (RUNNING または SHUTDOWN ) -> STOP に切り替わります。

4. TIDYING
(1) 状態の説明: すべてのタスクが終了し、ctl によって記録される「タスク数」が 0 になると、スレッド プールは TIDYING 状態に変わります。スレッドプールがTIDYING状態になるとフック関数terminated()が実行されます。スレッド プールが TIDYING になったときに対応する処理を実行したい場合は、ThreadPoolExecutor クラスのterminated() が空になっており、terminated() 関数をオーバーロードすることで実現できます。
(2) 状態切り替え: スレッドプールが SHUTDOWN 状態で、ブロッキングキューが空で、スレッドプール内で実行されるタスクも空の場合、SHUTDOWN -> TIDYING となります。スレッドプールが STOP 状態で、スレッドプール内で実行されるタスクが空の場合は、STOP -> TIDYING となります。

5. TERMINATED
(1) 状態説明: スレッドプールが完全に終了すると、TERMINATED 状態になります。
(2) 状態切り替え:スレッドプールが TIDYING 状態の場合、terminated()実行後、TIDYING -> TERMINATED となります。
TERMINATED に入る条件は次のとおりです。

  • スレッド プールは RUNNING 状態ではありません。
  • スレッド プールの状態は TIDYING 状態または TERMINATED 状態ではありません。
  • スレッド プールのステータスが SHUTDOWN で、workerQueue が空の場合。
  • ワーカーカウントは 0 です。
  • TIDYING ステータスを正常に設定しました。
    ここに画像の説明を挿入

スレッドプールエグゼキュータ

スレッドプールの作成

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

タスク投入
1. public voidexecute() //タスク投入時の戻り値なし
2.public Future<?> submit() //タスク実行完了後に戻り値あり

パラメータは、
corePoolSizeスレッド プール内のコア スレッドの数を説明します。タスクが送信されると、スレッド プールは、現在のスレッド数が corePoolSize に等しくなるまでタスクを実行する新しいスレッドを作成します。現在のスレッド数が corePoolSize の場合、送信され続けるタスクはブロッキング キューに保存され、実行を待機します。スレッド プールの prestartAllCoreThreads() メソッドが実行されると、スレッド プールはすべてのコア スレッドを事前に作成して開始します。

minimumPoolSizeスレッド プールで許可されるスレッドの最大数。現在のブロッキング キューがいっぱいでタスクの送信が継続される場合、現在のスレッド数が maxPoolSize 未満であれば、タスクを実行するための新しいスレッドが作成されます。

keepAliveTimeスレッド プール メンテナンス スレッドによって許可されるアイドル時間。スレッド プール内のスレッドの数が corePoolSize より大きい場合、この時点で新しいタスクの送信がない場合、コア スレッドの外側のスレッドはすぐには破棄されず、待機時間が keepAliveTime を超えるまで待機します。

workQueue は、実行を待機しているタスクのブロッキング キューを保存するために使用され、タスクは Runable インターフェイスを実装する必要があります。JDKでは
次のブロッキング キューが提供されます:
1. ArrayBlockingQueue: 配列構造に基づいてタスクを並べ替える、境界付きブロッキング キューFIFO;
2. LinkedBlockingQuene : リンク リスト構造に基づくブロッキング キュー、FIFO によるタスクの並べ替え、スループットは
通常 ArrayBlockingQuene よりも高い;
3、SynchronousQuene: 要素を格納しないブロッキング キュー、各挿入操作は別のスレッドが削除操作を呼び出すまで待機する必要がある操作、それ以外の場合は挿入 操作は常にブロック状態にあり、スループットは通常
LinkedBlockingQuene よりも高くなります; 4. priorityBlockingQuene: 優先順位を持つ無制限のブロッキング キュー; threadFactory は、
新しいスレッドの作成に使用される ThreadFactory 型の変数です。Executors.defaultThreadFactory() は、スレッドの作成にデフォルトで使用されます。デフォルトの ThreadFactory を使用してスレッドを作成すると、新しく作成されたスレッドは同じ NORM_PRIORITY 優先度を持ち、非デーモン スレッドとなり、スレッド名も設定されます。

ハンドラースレッド プールの飽和戦略。ブロッキング キューがいっぱいで、アイドル状態のワーカー スレッドがない場合、タスクが引き続き送信される場合、
タスクを処理するために戦略を採用する必要があります。スレッド プールには 4 つの戦略が用意されています
。 AbortPolicy: 例外を直接スローする、デフォルト ポリシー;
2. CallerRunsPolicy: 呼び出し元がタスクを実行するスレッドを使用する;
3. DiscardOldestPolicy: ブロッキング キューの最前面のタスクを破棄し、現在のタスクを実行する;
4. DiscardPolicy: タスクを破棄する直接;
上記 4 つのポリシー どちらも ThreadPoolExecutor の内部クラスです。
もちろん、アプリケーションのシナリオに応じて RejectedExecutionHandler インターフェイスを実装し、処理できないタスクのログ記録や永続的な保存などの飽和戦略をカスタマイズすることもできます。

スレッド プールの監視
public long getTaskCount() //スレッド プール内の実行済みおよび未実行のタスクの総数 public long getCompletedTaskCount() //完了したタスクの数 public int getPoolSize() //スレッド プール内の現在のスレッド数public int getActiveCount(
) //スレッドプール内のタスクを実行しているスレッドの数

スレッドプールの原理
ここに画像の説明を挿入

ソースコード分析:

execute方法
public void execute(Runnable command) {
    
    
    if (command == null)
        throw new NullPointerException();
   /*
* clt记录着runState和workerCount
    */
    int c = ctl.get();
/*
* workerCountOf方法取出低29位的值,表示当前活动的线程数;
* 如果当前活动线程数小于corePoolSize,则新建一个线程放入线程池中; * 并把任务添加到该线程中。
*/
    if (workerCountOf(c) < corePoolSize) {
    
    
       /*
* addWorker中的第二个参数表示限制添加线程的数量是根据corePoolSize 来判断还是maximumPoolSize来判断;
* 如果为true,根据corePoolSize来判断;
* 如果为false,则根据maximumPoolSize来判断 */
if (addWorker(command, true))
            return;
/*
* 如果添加失败,则重新获取ctl值 */
        c = ctl.get();
    }
/*
* 如果当前线程池是运行状态并且任务添加到队列成功 */
if (isRunning(c) && workQueue.offer(command)) {
    
     // 重新获取ctl值
int recheck = ctl.get();
// 再次判断线程池的运行状态,如果不是运行状态,由于之前已经把command
添加到workQueue中了,
// 这时需要移除该command
// 执行过后通过handler使用拒绝策略对该任务进行处理,整个方法返回 if (! isRunning(recheck) && remove(command))
            reject(command);
       /*
* 获取线程池中的有效线程数,如果数量是0,则执行addWorker方法
* 这里传入的参数表示:
* 1. 第一个参数为null,表示在线程池中创建一个线程,但不去启动;
* 2. 第二个参数为false,将线程池的有限线程数量的上限设置为
maximumPoolSize,添加线程时根据maximumPoolSize来判断;
* 如果判断workerCount大于0,则直接返回,在workQueue中新增的
command会在将来的某个时刻被执行。 */
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
} /*
* 如果执行到这里,有两种情况:
* 1. 线程池已经不是RUNNING状态;
}
* 2. 线程池是RUNNING状态,但workerCount >= corePoolSize并且 workQueue已满。
* 这时,再次调用addWorker方法,但第二个参数传入为false,将线程池的有限 线程数量的上限设置为maximumPoolSize;
* 如果失败则拒绝该任务
 */
 else if (!addWorker(command, false))
     reject(command);
 }

簡単に言うと、execute() メソッドの実行時に状態が常に RUNNING の場合、実行プロセスは次のようになります。

  1. workerCount < corePoolSize の場合、新しく送信されたタスクを実行するスレッドを作成して開始します。
  2. workerCount >= corePoolSize で、スレッド プール内のブロッキング キューがいっぱいでない場合は、タスクをブロッキング キューに追加します。
  3. workerCount >= corePoolSize && workerCount < minimumPoolSize で、スレッド プール内のブロッキング キューがいっぱいの場合は、新しく送信されたタスクを実行するためのスレッドを作成して開始します。
  4. workerCount >= minimumPoolSize で、スレッド プール内のブロッキング キューがいっぱいの場合、タスクは拒否戦略に従って処理され、デフォルトの処理方法では例外が直接スローされます。
    ここでは、addWorker(null, false); に注意する必要があります。これは、スレッドを作成しますが、タスクは workQueue に追加されているため、タスクは渡されません。そのため、ワーカーが実行されると、タスクが直接取得されます。 workQueueから。したがって、workerCountOf(recheck) == 0; のときに addWorker(null, false) を実行して、スレッド プールに RUNNING 状態のタスクを実行するスレッドが必要であることを確認します。


ここに画像の説明を挿入
addWorkerメソッドaddWorker メソッドの主な作業は、
スレッド プールに新しいスレッドを作成して実行することです。firstTask パラメータは、新しく実行されるスレッドによって実行される最初のタスクを指定するために使用されます。スレッドを追加しました。スレッド化するときに、現在のアクティブなスレッドの数が corePoolSize 未満であるかどうかを判断します。false は、新しいスレッドを追加する前に、現在のアクティブなスレッドの数が maxPoolSize 未満であるかどうかを判断する必要があることを意味します。コードは次のとおりです。

private boolean addWorker(Runnable firstTask, boolean core) {
    
    
    retry:
    for (;;) {
    
    
        int c = ctl.get();
// 获取运行状态
int rs = runStateOf(c);
/*
* 这个if判断
* 如果rs >= SHUTDOWN,则表示此时不再接收新任务;
* 接着判断以下3个条件,只要有1个不满足,则返回false:
* 1. rs == SHUTDOWN,这时表示关闭状态,不再接受新提交的任务,但却 可以继续处理阻塞队列中已保存的任务
* 2. firsTask为空
* 3. 阻塞队列不为空 *
* 首先考虑rs == SHUTDOWN的情况
* 这种情况下不会接受新提交的任务,所以在firstTask不为空的时候会返回
false;
* 然后,如果firstTask为空,并且workQueue也为空,则返回false, * 因为队列中已
*/
        // Check if queue empty only if necessary.
     if (rs >= SHUTDOWN &&
             ! (rs == SHUTDOWN &&
                     firstTask == null &&
                     ! workQueue.isEmpty()))
         return false;
for (;;) {
    
    
// 获取线程数
int wc = workerCountOf(c);
// 如果wc超过CAPACITY,也就是ctl的低29位的最大值(二进制是29个1),返回false;
// 这里的core是addWorker方法的第二个参数,如果为true表示根据corePoolSize来比较,
// 如果为false则根据maximumPoolSize来比较。
            //
         if (wc >= CAPACITY ||
                 wc >= (core ? corePoolSize : maximumPoolSize))
             return false;
// 尝试增加workerCount,如果成功,则跳出第一个for循环 if (compareAndIncrementWorkerCount(c))
break retry;
// 如果增加workerCount失败,则重新获取ctl的值
c = ctl.get();  // Re­read ctl
// 如果当前的运行状态不等于rs,说明状态已被改变,返回第一个 for循环继续执行
            if (runStateOf(c) != rs)
                continue retry;
               // else CAS failed due to workerCount change; retry
inner loop }
    }
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
    
    
// 根据firstTask来创建Worker对象 w = new Worker(firstTask);
// 每一个Worker对象都会创建一个线程 final Thread t = w.thread;
if (t != null) {
    
    
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
    
    
int rs = runStateOf(ctl.get());
// rs < SHUTDOWN表示是RUNNING状态;
// 如果rs是RUNNING状态或者rs是SHUTDOWN状态并且firstTask为null,向线程池中添加线程。
// 因为在SHUTDOWN时不会在添加新的任务,但还是会执行workQueue中的任务startable
if (rs < SHUTDOWN ||
        (rs == SHUTDOWN && firstTask == null)) {
    
    
    if (t.isAlive()) // precheck that t is
throw new IllegalThreadStateException(); // workers是一个HashSet
workers.add(w);
int s = workers.size();
// largestPoolSize记录着线程池中出现过的最大线程数量
if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
    
    
                mainLock.unlock();
            }
            if (workerAdded) {
    
    
// 启动线程
t.start(); workerStarted = true;
} }
    } finally {
    
    
        if (! workerStarted)
            addWorkerFailed(w);
}
    return workerStarted;
}

Worker クラス
スレッド プール内の各スレッドは Worker オブジェクトにカプセル化されます。ThreadPool が保持するのは、実際には Worker オブジェクトのセットです。JDK のソース コードを参照してください。
Worker クラスは AQS を継承し、Runnable インターフェイスを実装します。firstTask 属性と thread 属性に注意してください。firstTask は、受信したタスクを保存するために使用します。thread は、コンストラクターの呼び出し時に ThreadFactory によって作成されたスレッドであり、タスクのスレッドを処理するために使用されます。
コンストラクターを呼び出すときは、タスクを渡す必要があります。ここでは、getThreadFactory().newThread(this); を使用して新しいスレッドを作成します。ワーカー自体が Runnable を継承するため、newThread メソッドによって渡されるパラメーターは this ですインターフェイスはスレッドであるため、Worker オブジェクトは開始時に Worker クラスの run メソッドを呼び出します。
ワーカーはAQSを継承し、AQSを利用して排他ロックの機能を実現します。それを実現するために ReentrantLock を使用しないのはなぜですか? tryAcquire メソッドは再入を許可せず、ReentrantLock は再入を許可していることがわかります。

  1. lock メソッドが排他ロックを取得すると、現在のスレッドがタスクを実行していることになります。
  2. タスクが実行中の場合、スレッドは中断されるべきではありません。
  3. スレッドが排他ロック状態にない場合、つまりアイドル状態にある場合は、スレッドがタスクを処理していないことを意味し、この時点でスレッドは中断される可能性があります。
  4. スレッド プールが shutdown メソッドまたは tryTerminate メソッドを実行すると、interruptIdleWorkers メソッドを呼び出してアイドル状態のスレッドに割り込みます。また、interruptIdleWorkers メソッドは tryLock メソッドを使用して、スレッド プール内のスレッドがアイドル状態かどうかを判断します。
  5. 非再入可能に設定されている理由は、setCorePoolSize などのスレッド プール制御メソッドを呼び出すときにタスクがロックを再取得しないようにするためです。ReentrantLock を使用すると再入可能となるため、setCorePoolSize などのスレッド プール制御メソッドがタスク内で呼び出される場合、実行中のスレッドが中断されます。

したがって、ワーカーは AQS から継承し、スレッドがアイドル状態かどうか、およびスレッドを中断できるかどうかを判断するために使用されます。
また、構築メソッド内で setState(-1); が実行され、状態変数が -1 に設定されますが、なぜこれを行うのかというと、AQS のデフォルトの状態が 0 であるためです。作成されましたが、タスクはまだ実行されていません。この時点では
中断されるべきではありません。tryAquire メソッドを見てください。

protected boolean tryAcquire(int unused) {
    
     //cas修改state,不可重入
    if (compareAndSetState(0, 1)) {
    
    
        setExclusiveOwnerThread(Thread.currentThread());
        return true;
}
    return false;
}

tryAcquire メソッドは状態が 0 かどうかで判断するので、setState(-1); 状態を -1 に設定することは、タスク実行前のスレッドの中断を禁止することになります。
このため、runWorker メソッドでは、Worker オブジェクトの lock メソッドが最初に呼び出され、状態が 0 に設定されます。
runWorker メソッド
Worker クラスの run メソッドは、タスクを実行するために runWorker メソッドを呼び出します。runWorker メソッドのコードは次のとおりです。

    final void runWorker(Worker w) {
    
    
        Thread wt = Thread.currentThread();
        // 获取第一个任务
        Runnable task = w.firstTask;
        w.firstTask = null;
        // 允许中断
        w.unlock(); // allow interrupts
        // 是否因为异常退出循环
        boolean completedAbruptly = true;
        try {
    
    
            // 如果task为空,那么使用getTask获取任务
            while (task != null || (task = getTask()) != null) {
    
    
                w.lock();
                if ((runStateAtLeast(ctl.get(), STOP) ||
                        (Thread.interrupted() &&
                                runStateAtLeast(ctl.get(), STOP))) &&
                        !wt.isInterrupted())
                    wt.interrupt();
                try {
    
    
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
    
    
                        task.run();
                    } catch (RuntimeException x) {
    
    
                        thrown = x;
                        throw x;
                    } catch (Error x) {
    
    
                        thrown = x;
                        throw x;
                    } catch (Throwable x) {
    
    
                        thrown = x;
                        throw new Error(x);
                    } finally {
    
    
                        afterExecute(task, thrown);
                    }
                } finally {
    
    
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
    
    
            processWorkerExit(w, completedAbruptly);
        }
    }

最初の if 判定の説明は次のとおりです。スレッド プールが停止している場合は、現在のスレッドが中断状態にあることを確認し、そうでない場合は、現在のスレッドが中断状態にないことを確認します。

ここで、if ステートメントの実行中に shutdownNow メソッドも実行される可能性があることを考慮する必要があります。shutdownNow メソッドは状態を STOP に設定します。STOP 状態を確認してください。新しいタスクを受け入れることも、キュー内のタスクを処理することもできません
。タスクを処理しているスレッドを中断します。スレッド プールが RUNNING または SHUTDOWN 状態にある場合、shutdownNow() メソッドを呼び出すと、スレッド プールはその状態になります。

STOP 状態は、スレッド プール内のすべてのスレッドを中断します。ここでは、Thread.interrupted() を使用して、スレッドが RUNNING 状態または SHUTDOWN 状態で中断されていない状態にあるかどうかを判断します。 () メソッドは中断状態をリセットします。

runWorker メソッドの実行プロセスを要約します。

  1. while ループは getTask() メソッドを通じてタスクを継続的に取得します。
  2. getTask() メソッドは、ブロッキング キューからタスクをフェッチします。
  3. スレッド プールが停止している場合は、現在のスレッドが中断状態にあることを確認します。停止している場合は、現在のスレッドが中断状態にないことを確認します。
  4. task.run() を呼び出してタスクを実行します。
  5. タスクが null の場合は、ループから抜け出して processWorkerExit() メソッドを実行します。
  6. runWorker メソッドの実行は、Worker での run メソッドの実行が完了し、スレッドが破棄されることも意味します。
    ここでの beforeExecute メソッドと afterExecute メソッドは ThreadPoolExecutor クラスでは空であり、実装はサブクラスに任されています。
    completedAbruptly 変数はタスクの実行中に例外が発生したかどうかを示し、この変数の値は processWorkerExit メソッドで判断されます。

getTask メソッド
getTask メソッドは、ブロッキング キューからタスクをフェッチするために使用されます。コードは次のとおりです。

private Runnable getTask() {
    
    
	//轮询是否超时的标识
	boolean timedOut = false;
	//自旋for循环
	for (;;) {
    
    
		//获取ctl
		int c = ctl.get();
		//获取线程池的状态
		int rs = runStateOf(c);
 
		//检测任务队列是否在线程池停止或关闭的时候为空
		//也就是说任务队列是否在线程池未正常运行时为空
		if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
    
    
			//减少Worker线程的数量
			decrementWorkerCount();
			return null;
		}
		//获取线程池中线程的数量
		int wc = workerCountOf(c);
 
		//检测当前线程池中的线程数量是否大于corePoolSize的值或者是否正在等待执行任务
		boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
 
		//如果线程池中的线程数量大于corePoolSize
		//获取大于corePoolSize或者是否正在等待执行任务并且轮询超时
		//并且当前线程池中的线程数量大于1或者任务队列为空
		if ((wc > maximumPoolSize || (timed && timedOut))
			&& (wc > 1 || workQueue.isEmpty())) {
    
    
			//成功减少线程池中的工作线程数量
			if (compareAndDecrementWorkerCount(c))
				return null;
			continue;
		}
 
		try {
    
    
			//从任务队列中获取任务
			Runnable r = timed ?
				workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
				workQueue.take();
			//任务不为空直接返回任务
			if (r != null)
				return r;
			timedOut = true;
		} catch (InterruptedException retry) {
    
    
			timedOut = false;
		}
	}
}

ここで重要なのは 2 番目の if 判定で、目的はスレッド プール内の有効スレッド数を制御することです。上記の分析から、execute メソッドの実行時に、現在のスレッド プールのスレッド数が corePoolSize を超え、maximumPoolSize 未満で、workQueue がいっぱいの場合は、ワーカー スレッドを増やすことができますが、タイムアウトが発生した場合は、ワーカー スレッドを増やすことができます。タスクを取得しません。つまり、timedOut が true の場合、workQueue が空であることを意味します。これは、現在のスレッド プールにタスクを実行するスレッドがこれ以上ないことを意味します。corePoolSize より多くのスレッドを破棄し、 corePoolSize でのスレッドの数。

いつ破棄されるのでしょうか? もちろん、runWorker メソッドが実行された後、つまり Worker 内の run メソッドが実行された後、JVM によって自動的にリサイクルされます。

getTask メソッドが null を返すと、while ループが runWorker メソッドから飛び出し、processWorkerExit メソッドが実行されます。

processWorkerExit方法

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    
    
         // 因为抛出用户异常导致线程终结,直接使工作线程数减1即可
         // 如果没有任何异常抛出的情况下是通过getTask()返回null引导线程 

         //正常跳出runWorker()方法的while死循环从而正常终结,这种情况下,在getTask()中已经把线程数减1
         if (completedAbruptly)// If abrupt, then workerCount wasn't adjusted

                  decrementWorkerCount();

         final ReentrantLock mainLock = this.mainLock;
         mainLock.lock();
         try {
    
    
              // 全局的已完成任务记录数加上此将要终结的Worker中的已完成任务数
              completedTaskCount += w.completedTasks;
              // 工作线程集合中移除此将要终结的Worker
              workers.remove(w);
         } finally {
    
    
             mainLock.unlock();
         }
    
        // 见下一小节分析,用于根据当前线程池的状态判断是否需要进行线程池terminate处理
        tryTerminate();

        int c = ctl.get();
        // 如果线程池的状态小于STOP,也就是处于RUNNING或者SHUTDOWN状态的前提下:
        // 1.如果线程不是由于抛出用户异常终结,如果允许核心线程超时,则保持线程池中至少存在一个工作线程
        // 2.如果线程由于抛出用户异常终结,或者当前工作线程数,那么直接添加一个新的非核心线程
        if (runStateLessThan(c, STOP)) {
    
    
             if (!completedAbruptly) {
    
    
                // 如果允许核心线程超时,最小值为0,否则为corePoolSize
               int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
               // 如果最小值为0,同时任务队列不空,则更新最小值为1
               if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                    // 工作线程数大于等于最小值,直接返回不新增非核心线程
                   if (workerCountOf(c) >= min)
                     return; // replacement not needed
         }
         addWorker(null, false);
       }
  }

これまでのところ、processWorkerExit が実行された後、ワーカー スレッドは破棄されます。上記がワーカー スレッド全体のライフ サイクルです。execute メソッドから開始して、Worker は ThreadFactory を使用して新しいワーカー スレッドを作成します。RunWorker は getTask を通じてタスクを取得し、 getTask が null を返した場合は、図に示すように processWorkerExit メソッドを入力すると、スレッド全体が終了します
ここに画像の説明を挿入

  • スレッドの作成、タスクの送信、状態遷移、およびスレッド プールのシャットダウンを分析しました。
  • ここでは、スレッド プールのワークフローを拡張するために、execute メソッドが使用されています。execute メソッドは、corePoolSize、maximumPoolSize、およびブロッキング キューのサイズを使用して、受信したタスクをすぐに実行するか、ブロッキング キューに追加するか、拒否するかを決定します。 。
  • スレッドプールがクローズされるときのプロセスを紹介し、shutdown メソッドと getTask メソッドの間の競合状態も分析します。
  • タスクを取得するとき、スレッド プールの状態を使用して、ワーカー スレッドを終了する必要があるか、ブロックされたスレッドが新しいタスクを待機しているかを判断する必要があります。また、スレッド プールが閉じられているときにワーカー スレッドが中断される理由も説明します。各ワーカーにロックが必要な理由。

おすすめ

転載: blog.csdn.net/qq_39513430/article/details/112015277