Javaスレッドプールの実装原理とソースコード分析

Javaスレッドプールの実装原理とソースコード分析


序文

この記事は 2019 年 11 月下旬に書き始め、2020 年末まで延期され、2021 年の初めまで完成しませんでした。

時間が早すぎて遅すぎ~!

2019 年 10 月、あるドンがスタートアップ企業を辞めて就職面接を予定していたとき、彼は私にスレッドプールを尋ねたのをぼんやりと覚えています。それから彼は、私が 2017 年に書いた「Java Concurrent Programming Thread Pool Essential Knowledge Points」というメモを送ってくれました。その時は、スレッドプールがほとんどあると思っていました~!

2019 年 11 月 9 日、あるドンと私は大王路から 815 番のバスに乗り、ヤンジャオに行きました。その時は、マルチスレッドに関する知識を学んでいただけで、何もすることがなかったので、バスでおしゃべりをしただけでした。その際、スレッドプールについていくつか質問をいただきましたが、通常のワーキングスレッドプールでは、使い方がわかればせいぜいコアスレッド数を最適化できると思います。主な議論は、マルチスレッドの並行性とロックに関連しています。

年末は仕事が忙しくて独学することはほとんどなかったのですが、一週間経った頃、Dong Dong さんからの質問を思い出しました。実行を待ちますか?」

この作品の論理がよくわからないので、とりあえずこのTODO項目を記録しておき、時間ができたら勉強しようと思います。その結果、これは 2019 年と 2020 年にまたがり、2021 年に直接もたらされました。

長文で申し訳ありませんが、重要な点は、この記事の期間が長すぎて、私に深い印象を残したということです。話さなきゃいけないから、本題に入ろう~!

JDK1.8Java スレッド プールのコア設計と実装を分析するためのソース コード。

この記事では、Java スレッド プールの実装原理と Meituan のビジネスにおける実践について説明します。

Javaスレッドプールの実装原理とMeituanビジネスにおけるその実践この記事は非常によく書かれており、この記事の内容に加えて、この記事ではスレッドプールの背景、ビジネスにおけるスレッドプールの実践とダイナミクスについても説明しています。これらの種類のスレッド プールについて知りたい場合は、Java スレッド プールの実装原理と Meituan のビジネスにおけるその実践に関する記事を読むことができます。

読者がサーバーサイドの開発をしている学生であれば、Java スレッドプールの実装原理と Meituan ビジネスでの実践を読むことを強くお勧めします。

外観

外観は主に、スレッドプールを使用するときに通常目にするいくつかのポイントです。

  • 相続関係;
  • コンストラクタ;
  • コンストラクターのパラメーター。
  • コンストラクターのブロッキング キュー。
  • スレッドプールの作成;
  • コンストラクターでポリシーを拒否します。

スレッドプールの継承

ThreadPoolExecutor-uml.png

ThreadPoolExecutor実装されたトップレベルのインターフェースはExecutorExecutorユーザーがスレッドを作成する方法やタスクを実行するスレッドをスケジュールする方法に注意を払う必要がないことです. ユーザーは、オブジェクトを提供し、Runnableタスク操作ロジックをエグゼキューターに送信するだけで済みますExecutor.Executorフレームワークは、スレッドの展開とタスク部分の実行を完了します。

ExecutorServiceインターフェイスにはいくつかの機能が追加されます。

  1. タスクを実行する機能を拡張し、Future1 つまたは非同期タスクのバッチに対して生成できるメソッドを補足します。
  2. スレッド プールの操作の停止など、スレッド プールを管理および制御するためのメソッドを提供します。

AbstractExecutorServiceこれは、タスクを実行するプロセスを連続して接続する上位層の抽象クラスであり、下位層の実装がタスクを実行する 1 つの方法にのみ集中する必要があることを保証します。

最下位レベルの実装クラスは、ThreadPoolExecutor最も複雑な操作部分を実装します。

  1. 指定した数のスレッドのセットを自動的に作成、管理、再利用でき、該当する側はタスクを送信するだけで済みます

  2. スレッド セーフ、ThreadPoolExecutor内部状態、コア スレッド数、非コア スレッドおよびその他の属性、CASおよびAQSロック メカニズムの広範な使用による同時実行による競合の回避

  3. コア スレッド、バッファー ブロッキング キュー、非コア スレッド、および破棄戦略の概念を提供します。これらは、実際のアプリケーション シナリオに従って組み合わせて使用​​できます。

  4. スレッドプールの機能拡張を提供しbeforeExecute、サポートすることができますafterExecute()

コンストラクタ

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 : スレッド プール内のコア スレッドの数. 一般に、タスクがあるかどうかに関係なく、それらは常にスレッド プールで生き残ります. のThreadPoolExecutorメソッドallowCoreThreadTimeOut(boolean value).true指定された時間内に新しいタスクがない場合はコアスレッドも終了し、この時間間隔は 3 番目の属性でkeepAliveTime指定されます。
  • maximumPoolSize : スレッド プールが対応できるスレッドの最大数. アクティブなスレッドの数がこの値に達すると、後続の新しいタスクがブロックされます.
  • keepAliveTime : スレッドがアイドル状態のときのタイムアウト期間を制御します. それを超えるとスレッドは終了します. 通常、非コア スレッドに使用されますThreadPoolExecutorallowCoreThreadTimeOut(boolean value)メソッド が に設定されているtrue場合にのみ、コア スレッドにも作用します。
  • unit :keepAliveTimeパラメータの時間単位を指定するために使用されます。TimeUnitこれはenum列挙型で、一般的に使用されるのは 、TimeUnit.HOURS(小时)TimeUnit.MINUTES(分钟)などです。TimeUnit.SECONDS(秒) TimeUnit.MILLISECONDS(毫秒)
  • workQueue : スレッド プールのタスク キュー。execute(Runnable command)タスクはRunnableスレッド プールのメソッドを通じてキューに格納されます。
  • threadFactory : スレッド ファクトリ。スレッド プールの新しいスレッドを作成するために使用されるインターフェイスです。
  • handler : 拒否戦略. いわゆる拒否戦略は、タスクがスレッド プールに追加されたときに、タスクを拒否するためにスレッド プールによって採用される対応する戦略を指します。

メンバー変数

/**
 * 任务阻塞队列 
 */
private final BlockingQueue<Runnable> workQueue; 
/**
 * 非公平的互斥锁(可重入锁)
 */
private final ReentrantLock mainLock = new ReentrantLock();
/**
 * 线程集合一个Worker对应一个线程,没有核心线程的说话,只有核心线程数
 */
private final HashSet<Worker> workers = new HashSet<Worker>();
/**
 * 配合mainLock通过Condition能够更加精细的控制多线程的休眠与唤醒
 */
private final Condition termination = mainLock.newCondition();
/**
 * 线程池中线程数量曾经达到过的最大值。
 */
private int largestPoolSize;  
/**
 * 已完成任务数量
 */
private long completedTaskCount;
/**
 * ThreadFactory对象,用于创建线程。
 */
private volatile ThreadFactory threadFactory;  
/**
 * 拒绝策略的处理句柄
 * 现在默认提供了CallerRunsPolicy、AbortPolicy、DiscardOldestPolicy、DiscardPolicy
 */
private volatile RejectedExecutionHandler handler;
/**
 * 线程池维护线程(超过核心线程数)所允许的空闲时间
 */
private volatile long keepAliveTime;
/**
 * 允许线程池中的核心线程超时进行销毁
 */
private volatile boolean allowCoreThreadTimeOut;  
/**
 * 线程池维护线程的最小数量,哪怕是空闲的  
 */
private volatile int corePoolSize;
/**
 * 线程池维护的最大线程数量,线程数超过这个数量之后新提交的任务就需要进入阻塞队列
 */
private volatile int maximumPoolSize;

スレッド プールを作成する

Executors一般的に使用されるいくつかのスレッド プールを取得するメソッドを提供します。

  • キャッシュ スレッド プール

newCachedThreadPool必要に応じて新しいスレッドを作成するスレッド プールですが、以前に構築されたスレッドが使用可能になると再利用されます。多くの短期間の非同期タスクを実行するプログラムの場合、これらのスレッド プールによってプログラムのパフォーマンスが向上することがよくあります。呼び出しは、以前に構築されたスレッドを再利用execute()します(スレッドが利用可能な場合)。既存のスレッドが利用できない場合、新しいスレッドが作成され、プールに追加されます。60 秒間使用されていないスレッドを終了し、キャッシュから削除します。したがって、長時間アイドル状態のスレッド プールはリソースを使用しません。ThreadPoolExecutorコンストラクターを使用して、プロパティは似ているが詳細 (タイムアウトパラメーターなど) が異なるスレッドプールを作成できることに注意してください。

public static ExecutorService newCachedThreadPool() {
    
    
  return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                  60L, TimeUnit.SECONDS,
                  new SynchronousQueue<Runnable>());
}
  • シングルスレッドのスレッドプール

newSingleThreadExecutorシングル スレッド プールを作成します, つまり, スレッド プールには 1 つのスレッドしか機能せず, すべてのタスクがシリアルに実行されます. 唯一のスレッドが異常終了した場合, 新しいスレッドがそれを置き換えます. このスレッド プールは、すべての実行順序を保証します.タスクは、タスクが送信された順序で実行されます。

public static ExecutorService newSingleThreadExecutor() {
    
    
  return new FinalizableDelegatedExecutorService
    (new ThreadPoolExecutor(1, 1,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>()));
}
  • 固定サイズのスレッド プール

newFixedThreadPool固定サイズのスレッド プールを作成し、スレッドがスレッド プールの最大サイズに達するまで、タスクが投入されるたびにスレッドを作成します。スレッド プールが最大サイズに達すると、スレッド プールは変更されません。異常な実行, その後、スレッドプールは新しいスレッドを追加します.

public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
    
    
  return new ThreadPoolExecutor(nThreads, nThreads,
                  0L, TimeUnit.MILLISECONDS,
                  new LinkedBlockingQueue<Runnable>(),
                  threadFactory);
}
  • シングルスレッドのスレッドプール

newScheduledThreadPoolタスクのタイミングと定期的な実行をサポートする無制限のサイズのスレッド プールを作成します。

public static ScheduledExecutorService newScheduledThreadPool(
    int corePoolSize, ThreadFactory threadFactory) {
  return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
public ScheduledThreadPoolExecutor(int corePoolSize,
                   ThreadFactory threadFactory) {
  super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
      new DelayedWorkQueue(), threadFactory);
}

上記の方法では合計 、DelayedWorkQueueおよびLinkedBlockingQueueが使用されていることがわかりますSynchronousQueueこれは、スレッド コアの 1 つであるブロッキング キューです。

タスク ブロッキング キュー

BlockingQueue.png

一般に、直接送信キュー、制限付きタスク キュー、制限なしタスク キュー、および優先タスク キューに分けられます。

同期キュー

1.直接送信キュー:SynchronousQueueキューとして設定され、SynchronousQueue特別なキューでありBlockingQueue、容量がなく、挿入操作が実行されるたびにブロックされ、別の削除操作が実行された後にのみ目覚めます。それ以外の場合はすべての削除操作は、対応する挿入操作を待機する必要があります。

キューを使用するとSynchronousQueue、送信されたタスクは保存されず、常にすぐに実行のために送信されます。タスクの実行に使用されるスレッド数が 未満の場合はmaximumPoolSize、新しいプロセスの作成を試みます。maximumPoolSize設定された最大値に達すると、handler設定したポリシーに従って実行が拒否されます。したがって、この方法でサブミットしたタスクはキャッシュされませんが、すぐに実行されます. この場合、適切な数を設定する前に、プログラムの並行性を正確に評価する必要があります. そうしないと簡単ですmaximumPoolSize.拒否ポリシーが実装されます。

ArrayBlockingQueue

2.制限付きタスク キュー: 制限付きタスク キューはArrayBlockingQueue次のように実装できます。

pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(10),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

バインドされたタスク キューを使用してArrayBlockingQueue、新しいタスクを実行する必要がある場合、スレッド プールは新しいスレッドを作成し、作成されたスレッドの数が に達するまで、corePoolSize新しいタスクは待機キューに追加されます。待機キューがいっぱい、つまりArrayBlockingQueue初期化された容量を超えた場合は、スレッド数がmaximumPoolSize設定された最大スレッド数に達するまでスレッドを作成し続け、それよりも大きい場合はmaximumPoolSize拒否戦略を実行します。この場合、スレッド数の上限は束縛タスクキューの状態に直結します. 束縛キューの初期容量が大きい場合や、過負荷状態に達していない場合、スレッド数は常にe. それ以外の場合、タスクcorePoolSizキューがいっぱいになると、スレッドの最大数が制限されmaximumPoolSizeます。

LinkedBlockingQueue

3.無制限のタスク キュー: 無制限のタスク キューは次のように実装できますLinkedBlockingQueue

pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

無制限のタスク キューを使用すると、スレッド プールのタスク キューは無制限に新しいタスクを追加できます。スレッド プールによって作成されるスレッドの最大数は、設定した数です。つまり、この場合、このパラメーターは無効corePoolSizeですmaximumPoolSize。タスクキューには未実行のタスクがたくさんキャッシュされています. スレッドプール内のスレッド数が に達すると, それcorePoolSize以上増えることはありません. 後で新しいタスクが追加された場合, そのままキューに入って待機します.このタスク キュー モードでは、タスクの送信と処理の間の調整と制御に注意してください。そうしないと、キュー内のタスクが時間内に処理できず、リソースが終了するまでタスクが成長し続けるという問題が発生します。疲れ果てています。

PriorityBlockingQueue

4.プライオリティ タスク キュー: プライオリティ タスク キューは、PriorityBlockingQueue以下によって実装されます。

タスクは優先順位に従って再配置され、実行されます。スレッド プール内のスレッドの数は常にcorePoolSize、つまり 1 つだけです。

PriorityBlockingQueue実際、これは特別な無制限のキューであり、タスクがいくら追加されても、スレッド プールによって作成されるスレッドの数は最大数を超えることはありませんが、他のキューは通常、先入れ先順に従ってタスクを処理しますcorePoolSize。 -out ルール、PriorityBlockingQueueキューはカスタマイズ可能 ルールは、タスクの優先順位に従って順番に実行されます。

実際、LinkedBlockingQueue制限も設定でき、デフォルトの制限は ですInteger.MAX_VALUE同時に、構築時のキューサイズの設定もサポートしています。

拒否ポリシー

public interface RejectedExecutionHandler {
    
    
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

クローズされたときExecutor、つまりメソッドが実行された後executorService.shutdown()、またはExecutor制限された境界がスレッドとワーク キューの最大容量に使用され、飽和状態になったとき。メソッドを使用しexecute()て送信された新しいタスクは拒否されます.
上記の場合、executeメソッドはそのメソッドをRejectedExecutionHandler呼び出します.RejectedExecutionHandler.rejectedExecution(java.lang.Runnable, java.util.concurrent.ThreadPoolExecutor)

AbortPolicy デフォルトの拒否ポリシー

終了ポリシーとも呼ばれる拒否は、ランタイムをスローしますRejectedExecutionExceptionビジネス側は、例外をキャッチすることにより、このタスクに対して送信された結果についてタイムリーなフィードバックを得ることができます。

public static class AbortPolicy implements RejectedExecutionHandler {
    
    
  public AbortPolicy() {
    
     }
  public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    
    
    throw new RejectedExecutionException("Task " + r.toString() +
                                         " rejected from " +
                                         e.toString());
  }
}

CallerRunsPolicy

提出者が提出タスクを実行できるようにする自律的なフィードバック制御があると、新しいタスクの提出が遅くなる可能性があります。この場合、すべてのタスクを実行させる必要があります。

public static class CallerRunsPolicy implements RejectedExecutionHandler {
    
    
    public CallerRunsPolicy() {
    
     }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    
    
        if (!e.isShutdown()) {
    
    
            r.run();
        }
    }
}

DiscardPolicy

タスクを拒否し、黙ってタスクを破棄するためのハンドラー。この戦略を使用すると、システムの異常な状態を認識できない場合があります。注意して使用してください〜!

public static class DiscardPolicy implements RejectedExecutionHandler {
    
    
    public DiscardPolicy() {
    
     }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    
    
    }
}

DiscardOldestPolicy

キューの最前列のタスクを破棄し、拒否されたタスクを再送信します。この戦略を使うかどうかは、ビジネスを新旧に置き換える必要があるかどうかにかかっているので、慎重に使用してください~!

public static class DiscardOldestPolicy implements RejectedExecutionHandler {
    
    
    public DiscardOldestPolicy() {
    
     }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    
    
        if (!e.isShutdown()) {
    
    
            e.getQueue().poll();
            e.execute(r);
        }
    }
}

カーネル

先ほどスレッドプールの登場についてお話しましたが、その核心についてお話します

スレッド プールは、実際にはプロデューサー/コンシューマー モデルを内部的に構築します。このモデルは、タスクを適切にバッファリングしてスレッドを再利用するために、2 つ线程から分離され、直接関連しません。任务

スレッドプールの操作は、主にタスク管理とスレッド管理の 2 つの部分に分けられます。

タスク管理部分はプロデューサーとして機能し、タスクがサブミットされると、スレッドプールはタスクのその後の流れを判断します。

  1. タスクを実行するスレッドを直接申請します。
  2. キューにバッファリングし、スレッドが実行されるのを待ちます。
  3. タスクを拒否します。

スレッド管理部分はコンシューマーであり、スレッド プールで一様に維持され、タスクの要求に応じてスレッドが割り当てられ、スレッドがタスクを実行した後、実行する新しいタスクを取得し続けます。タスクを取得できない場合、スレッドはリサイクルされます。

次に、次の 3 つの部分に従って、スレッド プールの操作メカニズムについて詳しく説明します。

  1. スレッド プールが独自の状態を維持する方法。
  2. スレッド プールがタスクを管理する方法。
  3. スレッド プールがスレッドを管理する方法。

スレッドプールのライフサイクル

スレッド プールの実行状態は、ユーザーによって明示的に設定されませんが、スレッド プールの実行と共に内部的に維持されます。

スレッド プールは内部的に変数を使用して、実行ステータス ( runState) とスレッド数 ( workerCount) の 2 つの値を維持します。

特定の実装では、スレッド プールは、実行状態 ( runState) とスレッド数 ( )workerCountという 2 つの重要なパラメーターのメンテナンスをまとめます。

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

ctlこのAtomicIntegerタイプは、スレッド プールの実行ステータスとスレッド プール内の有効なスレッドの数を制御するフィールドです。

runStateスレッド プールの実行状態 ( ) とスレッド プール内の有効なスレッド数 ( )の 2 つの情報が同時に含まれており、workerCount上位3 ビットが保存されrunState、下位29ビットが保存されますworkerCount。 2 つの変数は互いに干渉しません。

1 つの変数を使用して 2 つの値を格納すると、関連する意思決定を行う際の不一致を回避でき、2 つの間の一貫性を維持するためにロック リソースを占有する必要がなくなります。また、スレッドプールのソースコードを読むと、スレッドプールの稼働状況とスレッド数を同時に判断する必要がある場合が多いことが分かります。スレッド プールには、ユーザーがスレッド プールの現在の実行ステータスとスレッド数を取得するためのメソッドもいくつか用意されています。ここではビット操作の方法が使用されており、基本操作よりもはるかに高速です (PS: この使用法は多くのソース コードで見られます)。

ライフサイクル状態を取得し、内部パッケージ内のスレッド プール スレッドの数を取得する計算方法は、次のコードに示されています。

private static final int COUNT_BITS = Integer.SIZE - 3;//32-3
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;//低29位都为1,高位都为0

// runState is stored in the high-order bits
private static final int RUNNING    = -1 << COUNT_BITS;//111
private static final int SHUTDOWN   =  0 << COUNT_BITS;//000
private static final int STOP       =  1 << COUNT_BITS;//001
private static final int TIDYING    =  2 << COUNT_BITS;//010
private static final int TERMINATED =  3 << COUNT_BITS;//011

// Packing and unpacking ctl
//计算当前运行状态,取高三位
private static int runStateOf(int c)     {
    
     return c & ~CAPACITY; }
//计算当前线程数量,取低29位
private static int workerCountOf(int c)  {
    
     return c & CAPACITY; }
//通过状态和线程数生成ctl
private static int ctlOf(int rs, int wc) {
    
     return rs | wc; }

ThreadPoolExecutor次の 5 つの実行状態があります。

稼働状況 ステータスの説明
ランニング 新しく送信されたタスクを受け入れることができ、ブロック キュー内のタスクを処理することもできます
シャットダウン 新しく送信されたタスクを受け入れることはできませんが、ブロック キュー内のタスクを処理し続けることはできます
ストップ 新しいタスクを受け入れることも、処理中のタスク スレッドを中断している間にキュー内のタスクを処理することもできません。
片付け 全タスク終了、workCount(有効スレッド数)は0
終了しました 終了したメソッドが実行された後、この状態に入ります

スレッドプールステートメント cycle.jpg

タスクスケジューリングメカニズム

タスク スケジューリングは、スレッド プールのメイン エントリ ポイントです。ユーザーがタスクを送信すると、そのタスクが次にどのように実行されるかは、このステージによって決定されます。この部分を理解することは、スレッドプールのコア動作メカニズムを理解することと同じです。

まず、すべてのタスクのスケジューリングがexecuteメソッドによって完了します.この部分で行われる作業は、現在のスレッド プールの実行状態実行中のスレッドの数、および実行中の戦略を確認し、次の実行プロセスを決定することです。 スレッドの実行を直接適用するか、実行のためにキューにバッファリングするか、タスクを直接拒否するか。その実行プロセスは次のとおりです。

  1. 最初にスレッドプールの実行ステータスを確認し、そうでない場合はRUNNING直接拒否します。スレッドプールは、RUNNINGタスクが特定の状態で実行されることを確認する必要があります。
  2. の場合workerCount < corePoolSize、スレッドが作成され、新しく送信されたタスクを実行するために開始されます。
  3. workerCount >= corePoolSizeで、スレッド プールのブロッキング キューがいっぱいでない場合は、タスクをブロッキング キューに追加します
  4. workerCount >= corePoolSize && workerCount < maximumPoolSizeで、スレッド プールのブロッキング キューがいっぱいの場合は、スレッドを作成して開始し、新しく送信されたタスクを実行します
  5. であり、スレッド プールのブロッキング キューがいっぱいの場合workerCount >= maximumPoolSize、タスクは拒否戦略に従って処理されます。デフォルトの処理方法は、例外を直接スローすることです。

タスクスケジューリングのフローチャート.png

次にソースコード解析時間を入力~!

タスクを提出する

//AbstractExecutorService.java
public <T> Future<T> submit(Callable<T> task) {
    
    
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);
    return ftask;
}
//ThreadPoolExecutor.java
public void execute(Runnable command) {
    
    
  if (command == null)
    throw new NullPointerException();
  int c = ctl.get();//获取ctl
  //检查当前核心线程数,是否小于核心线程数的大小限制
  if (workerCountOf(c) < corePoolSize) {
    
    
    //没有达到核心线程数的大小限制,那么添家核心线程执行该任务
    if (addWorker(command, true))
      return;
    //如果添加失败,刷新ctl值
    c = ctl.get();
  }
  //再次检查线程池的运行状态,将任务添加到等待队列中
  if (isRunning(c) && workQueue.offer(command)) {
    
    
    int recheck = ctl.get();//刷新ctl值
    //如果当前线程池的装不是运行状态,那么移除刚才添加的任务
    if (! isRunning(recheck) && remove(command))
      reject(command);//移除成功后,使用拒绝策略处理该任务;
    else if (workerCountOf(recheck) == 0)//当前工作线程数为0
      //线程池正在运行,或者移除任务失败。
      //添加一个非核心线程,并不指定该线程的运行任务。
      //等线程创建完成之后,会从等待队列中获取任务执行。
      addWorker(null, false);
  } 
  //逻辑到这里说明线程池已经不是RUNNING状态,或者等待队列已满,需要创建一个新的非核心线程执行该任务;
  //如果创建失败,那么非核心线程已满,使用拒绝策略处理该任务;
  else if (!addWorker(command, false))
    reject(command);
}

ワーカー スレッドを追加してタスクを実行する

private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
  Worker(Runnable firstTask) {
    
    
    setState(-1); // inhibit interrupts until runWorker
    this.firstTask = firstTask;//初始化的任务,可以为null
    this.thread = getThreadFactory().newThread(this);//Worker持有的线程
  }
  /**部分代码省略*/
	public void run() {
    
    
  	runWorker(this);
	}
}

ワーカーの追加とタスクの実行: 全体が作成されWorker、一致するものを見つけますRunnable

addwork.png

ワーカー スレッドを追加する

スレッドの追加は、スレッド プール の メソッドを介して行われますaddWorker. このメソッドの機能は、スレッドを追加することです. このメソッドは、スレッド プールがスレッドを追加する段階を考慮しません. スレッドを割り当てるこの戦略は、前の手順で完了します.ステップはスレッドを増やして終了し、実行させて、最後に成功したかどうかの結果を返すだけです。

addWorkerこのメソッドには 、 の 2 つのパラメーターがありますfirstTaskcore

firstTaskパラメータは、新しく追加されたスレッドによって実行される最初のタスクを指定するために使用されます。これは空にすることができます。

coreこのパラメーターはtrue、新しいスレッドを追加するときに、現在アクティブなスレッドの数が 未満であるかどうかを判断することを意味し、corePoolSize現在falseアクティブなスレッドの数が新しいスレッドを追加する前よりも少ないかどうかを判断する必要があることを意味しますmaximumPoolSize

addwork2.png

private boolean addWorker(Runnable firstTask, boolean core) {
    
    
    retry://breakcontinue的跳出标签
    for (;;) {
    
    
        int c = ctl.get();//获取ctl的值
        int rs = runStateOf(c);//获取当前线程池的状态;
        /**
         * 1、如果当前的线程池状态不是RUNNING
         * 2、当前线程池是RUNNING而且没有添加新任务,而且等待队列不为空。这种情况下是需要创建执行线程的。
         * 所以满足1,但不满足2就创建执行线程失败,返回false。
         */
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
                firstTask == null &&
                ! workQueue.isEmpty()))
            return false;
        /**进入内层循环 */
        for (;;) {
    
    
            int wc = workerCountOf(c);//获取当前执行线程的数量
            /**
             * 1、工作线程数量大于或等于计数器的最大阈值,那么创建执行线程失败,返回false。
             * 2、如果当前创建的核心线程,那么工作线程数大于corePoolSize的话,创建执行线程失败,返回false。
             * 3、如果当前创建的是非核心线程,那么工作线程数大于maximumPoolSize的话,创建执行线程失败,返回false。
             */
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            //用CAS操作让线程数加1,如果成功跳出整个循环
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();  // Re-read ctl
            if (runStateOf(c) != rs)//线程状态前后不一样,重新执行外循环
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
            //如果CAS操作由于工作线程数的增加失败,那么重新进行内循环
        }
    }
    /**就现在,线程数已经增加了。但是真正的线程对象还没有创建出来。*/
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
    
    
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
    
    
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();//加锁
            try {
    
    
                // Recheck while holding lock.
                // Back out on ThreadFactory failure or if
                // shut down before lock acquired.
                int rs = runStateOf(ctl.get());
                /**
                 * 再次检查线程池的运行状态
                 * 1、如果是RUNNING状态,那么可以创建;
                 * 2、如果是SHUTDOWN状态,但没有执行线程,可以创建(创建后执行等待队列中的任务)
                 */
                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 {
    
    
        //如果工作线程添加失败,那么进行失败处理
        //将已经增加的线程数减少,将添加到集合的工作线程删除
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

タスクを実行する

ワーカー スレッドを追加するセクションで、追加が成功すると、タスクを実行するためにスレッドが開始されることを確認しました。

runwork.png

final void runWorker(Worker w) {
    
    
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    //解锁,允许中断
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
    
    
        //如果当前的工作线程已经有执行任务,或者可以从等待队列中获取到执行任务
        //getTask获取任务时候会进行阻塞
        while (task != null || (task = getTask()) != null) {
    
    
            w.lock();//开始执行,上锁
            // If pool is stopping, ensure thread is interrupted;
            // if not, ensure thread is not interrupted.  This
            // requires a recheck in second case to deal with
            // shutdownNow race while clearing interrupt
            //判断线程是否需要中断
            //如果线程池状态是否为STOP\TIDYING\TERMINATED,同时当前线程没有被中断那么将当前线程进行中断
            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++;//当前工作线程执行完成的线程数+1
                w.unlock();//执行完成解锁
            }
        }
        completedAbruptly = false;//完成了所有任务,正常退出
    } finally {
    
    //执行工作线程的退出操作
        processWorkerExit(w, completedAbruptly);
    }
}

ワーカー スレッドがタスクを取得する

getTask.jpg

private Runnable getTask() {
    
    
    boolean timedOut = false; // Did the last poll() time out?
    for (;;) {
    
    
        int c = ctl.get();//获取ctl的值
        int rs = runStateOf(c);//获取线程池状态

        // Check if queue empty only if necessary.
        /**
         * 1、rs为STOP\TIDYING\TERMINATED,标识无法继续执行任务
         * 2、等待队列中没有任务可以被执行
         * 工作线程数量减一
         */
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
    
    
            decrementWorkerCount();
            return null;
        }
        int wc = workerCountOf(c);//获取工作线程数量
        // Are workers subject to culling?
        //如果允许核心线程超时,或者当前工作线程数量大于核心线程数量。标识需要进行超时检测
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
        /**
         * 1、如果当前工作线程数是否大于线程池可允许的最大工作线程数(maximumPoolSize可以动态设置)
         * ,或者当前需要进行超时控制并且上次从等待队列中获取执行任务发生了超时。
         * 2、如果当前不是唯一的线程,并且等待队列中没有需要执行的任务。
         * 这两种情况下一起存在就表示,工作线程发生了超时需要回收,所以对线程数进行-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;
        }
    }
}

ワーカー スレッドの終了

スレッド プール内のスレッドの破棄は、JVM の自動リサイクルに依存します. スレッド プールの仕事は、スレッド プールの現在の状態に応じて一定数のスレッド参照を維持し、これらのスレッドが JVM によってリサイクルされるのを防ぐことです. JVM. スレッドプールがどのスレッドをリサイクルする必要があるかを決定したら、その参照を削除するだけです。Worker作成後、継続的にポーリングを行い、タスクを取得して実行しますが、コアスレッドはタスクの取得を無期限に待機でき、非コアスレッドは限られた時間内にタスクを取得する必要があります。タスクを取得できない場合Worker、つまり、取得したタスクが空の場合、ループは終了し、Workerスレッド プール内の自身の参照を積極的に削除します。

processWorkerExit.png

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    
    
    //completedAbruptly为true,标识该工作线程执行出现了异常,将工作线程数减一
    if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
        decrementWorkerCount();
    //否则标识该工作线程为正常结束,这种情况下getTask方法中已经对工作线程进行了减一
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();//加锁
    try {
    
    
        completedTaskCount += w.completedTasks;//更新线程池的,线程执行完成数量
        workers.remove(w);//工作线程容器移除该工作线程
    } finally {
    
    
        mainLock.unlock();//解锁
    }
    //尝试结束线程池
    tryTerminate();
    int c = ctl.get();
    if (runStateLessThan(c, STOP)) {
    
    //如果当前线程池的运行状态是RUNNING\SHUTDOWN
        if (!completedAbruptly) {
    
    //如果该工作线程为正常结束
            /**
             * 判断当前需要的最少的核心线程数(如果允许核心线程超时,那么最小的核心线程数为0,否则为corePoolSize)
             */
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            //如果允许核心线程超时,而且等待队列不为空,那么工作线程的最小值为1,否则为0。
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
            //当前工作线程数,是否满足最先的核心线程数
            if (workerCountOf(c) >= min)
                //如果满足那么直接return
                return; // replacement not needed
        }
        //如果是异常结束,或者当前线程数不满足最小的核心线程数,那么添加一个非核心线程
        //核心线程和非核心线程没有什么不同,只是在创建的时候判断逻辑不同
        addWorker(null, false);
    }
}

特別なニーズ

スレッドプールの監視

スレッド プールによって提供されるパラメーターを監視します。スレッド プールを監視するときに使用できるスレッド プールの属性がいくつかあります。

  • getTaskCount: スレッド プール内の実行済みおよび未実行のタスクの総数。
  • getCompletedTaskCount: スレッド プールによって完了したタスクの数。値は以下ですtaskCount
  • getLargestPoolSize: スレッド プールによってこれまでに作成されたスレッドの最大数。このデータを通じて、スレッド プールがいっぱいかどうか、つまり、スレッド プールに達したかどうかを知ることができますmaximumPoolSize
  • getPoolSize: スレッド プール内の現在のスレッド数。
  • getActiveCount: スレッド プールで現在タスクを実行しているスレッドの数。

スレッド プールのサイズを動的に調整する

JDKメソッドの例として、スレッド プール ユーザーがインスタンスThreadPoolExecutorを介してスレッド プールのコア ポリシーを動的に設定できるようにします。setCorePoolSize

ランタイム スレッド プールのユーザーがこのメソッド設定を呼び出した後corePoolSize、スレッド プールは元のcorePoolSize値を直接上書きし、現在の値と元の値の比較に基づいて異なる処理戦略を採用します。

現在の値が現在の作業スレッド数よりも少ない場合は、冗長なworkerスレッドがあることを意味し、このとき現在のidleスレッドに割り込み要求を発行してworker再利用を実現し、冗長なスレッドも再利用します。次回はリサイクルされます; 現在の値が元の値よりも大きく、worker現在の状態です)スレッドは操作中にロックされているため、ロックを解放します)。idelworkeridelworker

setCorePoolSize.png

public void setCorePoolSize(int corePoolSize) {
    
    
    if (corePoolSize < 0)
        throw new IllegalArgumentException();
    //计算增量
    int delta = corePoolSize - this.corePoolSize;
    //覆盖原有的corePoolSize
    this.corePoolSize = corePoolSize;
    //如果当前的工作线程数量大于线程池的最大可运行核心线程数量,那么进行中断工作线程处理
    if (workerCountOf(ctl.get()) > corePoolSize)
        interruptIdleWorkers();
    else if (delta > 0) {
    
    //如果增量大于0
        // We don't really know how many new threads are "needed".
        // As a heuristic, prestart enough new workers (up to new
        // core size) to handle the current number of tasks in
        // queue, but stop if queue becomes empty while doing so.
        //等待队列非空,获取等待任务和增量的最小值
        int k = Math.min(delta, workQueue.size());
        //循环创建核心工作线程执行等待队列中的任务
        while (k-- > 0 && addWorker(null, true)) {
    
    
            if (workQueue.isEmpty())
                break;
        }
    }
}
private void interruptIdleWorkers() {
    
    
    interruptIdleWorkers(false);
}
private void interruptIdleWorkers(boolean onlyOne) {
    
    
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();//加锁
    try {
    
    
        //遍历工作线程的集合
        for (Worker w : workers) {
    
    
            Thread t = w.thread;
            //如果当前线程没有被中断,而且能获取到锁,那么尝试进行中断,最后释放锁
            if (!t.isInterrupted() && w.tryLock()) {
    
    
                try {
    
    
                    t.interrupt();
                } catch (SecurityException ignore) {
    
    
                } finally {
    
    
                    w.unlock();
                }
            }
            //是否仅仅中断一个工作线程
            if (onlyOne)
                break;
        }
    } finally {
    
    //释放锁
        mainLock.unlock();
    }
}

スレッド プールを正常にシャットダウンする

「スレッド プール ステートメント サイクル」図からも、メソッドを実行すると、スレッド プールの状態がRUNNINGからSHUTDOWNに変わることThreadPoolExecutor#shutdownわかります呼び出し後スレッド プールのステータスはRUNNINGからSTOPに変わりますThreadPoolExecutor#shutdownNow

シャットダウン

新しいタスクの受け入れを停止し、元のタスクは引き続き実行されます

  1. 新しいサブミット タスクの受信を停止します。
  2. 送信されたタスク (実行中のタスクとキューで待機中のタスクを含む) は引き続き実行されます。
  3. ステップ 2 が完了するまで待ってから、実際に停止してください。
public void shutdown() {
    
    
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
    
    
        checkShutdownAccess();// 检查权限
        advanceRunState(SHUTDOWN);// 设置线程池状态
        interruptIdleWorkers();// 中断空闲线程
        // 钩子函数,主要用于清理一些资源
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
    
    
        mainLock.unlock();
    }
    tryTerminate();
}

shutdownこのメソッドは、最初にロックし、次にシステムのインストール ステータスをチェックします。その後、スレッド プールの状態はSHUTDOWNに変更され、その後、スレッド プールはサブミットされた新しいタスクを受け入れなくなります。この時点で、タスクをスレッド プールに送信し続けると、スレッド プール拒否ポリシーが応答に使用されます。これは既定で使用され、例外ThreadPoolExecutor.AbortPolicyがスローされます。RejectedExecutionException

interruptIdleWorkersこのメソッドは、スレッド プールのサイズを動的に調整するセクションのソース コードに記載されています. アイドル スレッドのみを中断し、タスクを実行しているスレッドは中断しません。アイドル スレッドは、スレッド プールのブロッキング キューでブロックされます。

今すぐシャットダウン

新しいタスクの受け入れを停止すると、元のタスクの実行が停止します

  1. と同様shutdown() に、まず新しいsubmitタスクの受信を停止します。
  2. キューで待機中のタスクを無視します。
  3. 実行中のタスクを中断しようとしますinterrupt
  4. 実行されていないタスクのリストを返します。
public List<Runnable> shutdownNow() {
    
    
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
    
    
        checkShutdownAccess();// 检查状态
        advanceRunState(STOP);// 将线程池状态变为 STOP
        interruptWorkers();// 中断所有线程,包括工作线程以及空闲线程
        tasks = drainQueue();// 丢弃工作队列中存量任务
    } finally {
    
    
        mainLock.unlock();
    }
    tryTerminate();
    return tasks;
}
private void interruptWorkers() {
    
    
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
    
    
        for (Worker w : workers)
            //如果工作线程已经开始,那么调用interrupt进行中断
            w.interruptIfStarted();
    } finally {
    
    
        mainLock.unlock();
    }
}
private List<Runnable> drainQueue() {
    
    
    BlockingQueue<Runnable> q = workQueue;
    ArrayList<Runnable> taskList = new ArrayList<Runnable>();
    //从此队列中删除所有可用的元素,并将它们添加到给定的集合中。
    q.drainTo(taskList);
    //如果队列是DelayQueue或其他类型的队列,而poll或drainTo可能无法删除某些元素,则会将它们逐个删除。
    if (!q.isEmpty()) {
    
    
        for (Runnable r : q.toArray(new Runnable[0])) {
    
    
            if (q.remove(r))
                taskList.add(r);
        }
    }
    return taskList;
}

shutdownNowスレッドを終了しようとするメソッドは、Thread.interrupt()メソッド、効果は限定的です. スレッドにsleep, wait, , タイム ロックConditionなどのアプリケーションが存在しない場合、interrupt()メソッドは現在のスレッドを中断できません。したがって、shutdownNow() スレッド プールがすぐに終了できるわけではなく、終了する前に実行中のすべてのタスクが完了するまで待機する必要がある場合があります。しかし、ほとんどの場合、すぐに辞めることができます。

スレッド中断メカニズム:thread#interrupt中断フラグを設定するだけでは、通常のスレッドはすぐには中断されません。割り込みをすぐに有効にしたい場合は、スレッドを呼び出して、スレッドの割り込みステータスThread.interrupted()を判断する。ブロックされたスレッドの場合、割り込みが呼び出されると、スレッドはブロックされた状態をすぐに終了し、InterruptedException例外を。したがって、ブロックされたスレッドの場合、InterruptedException例外を。

awaitTermination

スレッド プールshutdownshutdownNowメソッドは、実行タスクの終了を積極的に待機しません. スレッド プール タスクの実行の終了まで待機する必要がある場合は、awaitTerminationアクティブに

  • 送信されたすべてのタスク (キューでの実行中および待機中を含む) が実行されるまで待ちます。
  • タイムアウトになるまで待ちます。
  • スレッドが中断され、スローされますInterruptedException

スレッド プール タスクの実行が終了した場合、awaitTerminationメソッドは戻りますtrue。それ以外の場合は、待機時間が指定された時間を超えると戻りますfalse

// 关闭线程池的钩子函数
private static void shutdown(ExecutorService executorService) {
    
    
    // 第一步:使新任务无法提交
    executorService.shutdown();
    try {
    
    
        // 第二步:等待未完成任务结束
        if(!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
    
    
             // 第三步:取消当前执行的任务
            executorService.shutdownNow();
            // 第四步:等待任务取消的响应
            if(!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
    
    
                System.err.println("Thread pool did not terminate");
            }
        }
    } catch(InterruptedException ie) {
    
    
        // 第五步:出现异常后,重新取消当前执行的任务
        executorService.shutdownNow();
        Thread.currentThread().interrupt(); // 设置本线程中断状态
    }
}

他の

内容が濃すぎて読みきれない感じ~!

スレッド プールについて話すときは、マルチスレッドの同時操作同步、、、、、、および同時実行制御に必要なその他の知識ポイントについて話さなければなりませ异步CSAAQS公平锁和非公平锁可重入锁和非可重入锁

日常業務ではめったに使用されません. 体系的な知識構造を持っていますか? たくさん学んだら忘れて、また学んで忘れる。

将来、一歩一歩学び、共有する機会が得られることを願っています。

記事はここですべて説明します。他に連絡が必要な場合は、メッセージを残してください〜!

おすすめ

転載: blog.csdn.net/stven_king/article/details/113870331
おすすめ