第21章ThreadPoolExecutorソースコードの理解

序文

しばらくJavaの基本的なコア知識を読んで学ぶ時間がありませんでした。主な理由は、最近転職して仕事を終え、結婚するのに忙しかったことです。10月に人生の主要なイベントを終えたところです。 。やっと休憩できると思ったのですが、ちょっとしたバイビーがまたやって来ました。、休む時間がないようです。休む時間がないので、一日の間隔でしか勉強する時間がありません。

次の章では、Javaのスレッドプールのソースコードといくつかの主成分分析を分析します。エラーがある場合は、修正してください。まず、スレッドプールで一般的に使用されるファミリシステムを見てください。
ここに画像の説明を挿入

一般的なファミリアーキテクチャでは、Executorはスレッドプールの最上位インターフェイスであり、ExecutorServiceもインターフェイスです。一般的に使用されるスレッドプールエンティティクラスは、ThreadPoolExecutor、ScheduledThreadPoolExecutor、およびExecutorsです。エグゼキュータはスレッドプールを作成するための多くのメソッドを提供しますが、それらの内部はすべてThreadPoolExecutorに基づいています。また、ThreadPoolExecutorから継承される時限スレッドプールであるScheduledThreadPoolExecutorもあるため、スレッドプールを研究する人にとっては、ThreadPoolExecutorの調査と分析が最優先事項です。

ThreadPoolExecutor序

巨大な機械のようですが、具体的な画像の描写が苦手なので、空母に例えています。
航空母艦の特徴は、非常に大きく、構成構造が非常に複雑で、操作メカニズムが煩雑であり、すべてプラットフォーム機能を備えていることです。ThreadPollExecutorは空母と同じです。空母として、いくつかの戦闘機を運ぶことができます。各戦闘機は異なるタスクを実行できます。また、異なる時間の多くの状態に対応します。
これは、空母ThreadPoolExecutorの一般的な説明です。以下は、一般的な分析を行うために、構造構成、動作原理、および実装メカニズムに分けられます。

1.構造構成

ThreadPollExecutorのスケルトンは、6つの部分で構成されています。

コアスレッド数、最大スレッド数、スレッド存続時間、タスクキュー、スレッドファクトリ、拒否戦略。
ここに画像の説明を挿入
ロボットみたいですか?パターンや形状、大型機械の形状などがありますが、ニーズに合わせて各パーツを設計する方法は、各パーツのパラメータを設計する必要があります。
では、各パラメーターの意味は何ですか?パラメータ間の関係は何ですか?

故事背景: 
近年来,索马里海盗猖獗,许多的商货运输轮船都受到劫持,部分轮船还有人员伤亡,为此某部队准备派遣一艘航空母舰前往打击这些海盗。

1.タスクキュー
workQueue:タスクを保存するためのキュー。

所有关于海盗的任务由总部收集信息后,发向航空母舰。航母可以设置一个任务接收队列。BlockingQueue 是一个阻塞队列的顶层接口,其实现有很多种,常见的几种队列:有界队列、无界队列、延迟队列、同步队列等等。

(1)制限付きキューの表現:ArrayBlockingQueueとLinkedBlockingQueue
には一定の長さがあります。ArrayBlockingQueueの最下層は配列によって実装され、サイズを指定する必要があります。LinkedBlockingQueueのデフォルトサイズはInteger.MAX_VALUEで、これは固定制限のあるタスクキューです。

(2)無制限キューの代表:PriorityBlockingQueue
は、境界や容量の兆候がないことを特徴としています。

设计好了任务系统后,下一步就是飞机了。
飞机是航母上的核心,那么看一下飞机怎么生产出来的,即线程池中的线程。

2.スレッドファクトリ
threadFactory:スレッドを作成するためのファクトリ。スレッドプール内の変数です。ThreadFactoryは、唯一のトップレベルメソッドnewThread()を定義するインターフェイスでもあります。
このスレッドファクトリを構成するには、次の2つの方法があります。
(1)一般的に使用されるスレッドファクトリ実装クラスDefaultThreadFactoryを使用します。
(2)自己定義クラスの実装は、ビジネスのニーズに合わせてよりパーソナライズできます。

飞机生产工厂有了,但是不可能让工厂无限制的生产,毕竟每架飞机耗费的资金巨大。所以需要根据业务的需要确定生产个数。
由于通常情况下,任务的数量是浮动的,所以生产的个数需要由两个参数来控制,即核心线程数和最大线程数。

3.コアスレッドの数
corePoolSize:初期コアワーカースレッドの数を表します。

在平时,航母上只装载了几架飞机,虽然索马里的海盗猖獗,但是好在不多,派出的飞机足以将他们打击。
但是临近过年了,平时不做海盗的平民也来当海盗了,越来越多的海盗出现在了索马里。航母上的飞机一下子忙不过来了。
这时候开始向总部请求支援飞机,但是最多可以支援多少呢,这取决于航母设计时甲板的容纳量。

4.スレッドの最大数
maximumPoolSize:コアスレッドに対応できるワーカースレッドの最大数を表します。
トリガー条件:コアスレッドが使い果たされ、タスクキューがいっぱいです。

增加了飞机后,有两个可能性。
(1)战斗力增强后,所有海盗都能够被打击了。
(2)即使甲板的飞机容量已经达到上限了,海盗们还是打击不过来。

按照上述第一个可能性,增派了飞机,海盗都能够被打击了,工作又恢复到平日里的样子。随着过年那段时间一过,平日里海盗又没有那么多了。
本该高兴的事情,舰长开始愁了,后面增派飞机后,每个月都要增加保养、加油、训练,还得让食堂多准备些馒头,这些飞行员真能吃,一口气吃仨个。
所以舰长想了想得让那些待业的飞机回去。

5.スレッドの存続時間
keepAliveTime:アイドル状態の後にスレッドがリサイクルされる時間の長さ。
単位:これは時間の単位です。keepAliveTimeと協力して時間を形成します。

这个参数是舰长想出来的一个损招,发出了一个全员通告:任何飞机,如果没有那么多海盗了,待机满XX天后必须返回总部。
这是发给所有飞机的一个共同的通告,但是为了维持日常任务需要,还是会留下核心数量多的飞机,这样食堂的馒头也不用抢了,舰长打着自己的小算盘。
但是既然是发给所有飞机的通告,那么当索马里没有海盗了,核心数量多的飞机需要回去吗?这取决于allowCoreThreadTimeOut这个参数是否为true。

6.拒否ポリシー
ハンドラー:スレッドプールがいっぱいになった後の拒否ポリシー。
トリガー条件:スレッドが使い果たされ、タスクキューがいっぱいになってからトリガーされます。したがって、無制限のブロッキングキューが使用されている場合、拒否戦略がトリガーされることはほとんどありません。

按照刚刚的第二种可能性,如果所有飞机都派完了,任务队列也装满了,总部不断发出的任务怎么办?

4つの拒否ポリシー、つまりAbortPolicy、DiscardPolicy、DiscardOldestPolicy、CallerRunsPolicyがあります。それらはすべてRejectedExecutionHandlerインターフェースを実装し、独自のrejectedExecution()メソッドを実装します。各拒否戦略に対するこのメソッドの実装は、特定の拒否実装の詳細です。

AbortPolicy:スレッドプールのデフォルトの拒否ポリシー。これは、例外が常にスローされることを意味します。
DiscardPolicy:そのrejectedExecution()メソッドは何もしないため、タスクが直接破棄されます。
CallerRunsPolicy:この種の拒否ポリシーは、スレッドが閉じられていないときに呼び出し元のスレッドでタスクを直接実行します。スレッドが閉じられている場合、タスクを破棄するのはDiscardPolicyのようなものです。
DiscardOldestPolicy:最後のタイプの拒否ポリシーは、スレッドプールが閉じられていない場合、新しいタスクがタスクキューの最後に追加され、キューの先頭にあるタスクが混雑することを意味します。スレッドプールが閉じている場合は、DiscardPolicyのようになります。

2.動作原理

上記のパラメータの特性に応じて、スレッドプールの操作は次の部分に分けられ、理解を容易にするために例が紹介されています。

ThreadPoolExecutor executor = new ThreadPoolExecutor(
										   (corePoolSize)5,
											(maximumPoolSize) 20,
											(keepAliveTime) 30,
											(unit) TimeUnit.SECONDS,
											(workQueue) new LinkedBlockingDeque<>(),
											(threadFactory) Executors.defaultThreadFactory(),)

(1)起動したばかりで、タスクはありません
起動するだけです。つまり、スレッドプールが作成されたばかりの場合、現時点ではスレッドプールにスレッドは作成されません。これは空のプールであり、現時点ではスタンバイ状態です。
ここに画像の説明を挿入

スレッドプールが作成された後、コアスレッドプールがすぐに作成されると考える人もいます。コアスレッドはいつ作成されますか?次のステップは特定の分析です。
(2)同時に受信したタスクの数<=コアスレッドの数[5]。
最初のタスクが入ると、以前はスレッドプールが空だったため、スレッドファクトリを使用して、このタスクを実行するスレッドをすぐに作成します。

思考? 按上面的理论,当第二个任务传来时,也会立即创建第二个线程吗?答:非也!

2番目のタスクが来て、最初のスレッドがまだ実行中の場合、スレッドプールは2番目のスレッドを作成します。最初のスレッドがアイドル状態の場合、最初のスレッドは引き続きタスクの実行に使用されます。
後者のスレッドについても同様です。この条件の範囲内で、再利用できる場合は再利用され、再利用できない場合は新しいコアスレッドが作成されます。

タスクの数が増え続ける場合は、次の分析を参照してください。

(3)同時に受信するタスクの数>コアスレッドの数の場合、a、b、c、dの4つのケースがあります。
a。タスク数<=(コアスレッド数[5] +タスクキュー長さ[2 ^ 31-1])
この場合、コアスレッドはすべて実行されているため、後続のタスクがタスクブロックキューに追加されます。
一部のコアスレッドがタスクの実行を終了すると、引き続きタスクをキューから取り出します。したがって、キュー内のタスクを取得する操作は、マルチスレッドの同時動作である可能性があります。スレッドの安全性を確保するために、ブロッキングキューが使用されます。従来のキューの代わりに。

b。タスクの数>(コアスレッドの数[5] +タスクキューの長さ[2 ^ 31-1])
タスクの突然の爆発的な増加により、キューがいっぱいになり、タスクの増加の需要を満たすことができなくなりました。
このとき、スレッドプールはタスクのニーズを満たすために一時的なスレッドを作成します。

思考:此时的临时线程执行的任务是队列中poll出来的任务,还是任务队列装满后多余出来的任务?

この問題は、execute(Runnable command)メソッドで確認できます。このとき、一時スレッドはキューに追加できないタスクを実行します。これは冗長タスクです。別のタスクがキューで消費されている限り、後続のタスクが最初にキューに追加されます。

c。タスク数<=(スレッドの最大数[20] +タスクキューの長さ[2 ^ 31-1])
スレッドプール内のスレッドはbの戦略に従います。一時的なスレッドが必要な場合、スレッドプールはこれらの一時スレッドの作成を続行します。作成されるスレッドの最大数は、パラメーターmaximumPoolSizeによって異なります。
このパラメータを超えることが判明した場合。次のような状況が発生します。

d。タスク数>(最大スレッド数[20] +タスクキューの長さ[2 ^ 31-1])
この時点でキューがいっぱいの場合、スレッド数は最大数に達しています。スレッドプールは、この状況に対して4つの拒否戦略を提供します。発信者は、拒否戦略を設定し、拒否戦略を判断することにより、ビジネス要件を実装できます。

(4)スレッド回復メカニズム
コアスレッドと一時スレッドの回復メカニズムは同じです。リサイクルメカニズムは2つの方法で決定されます。

  1. リサイクルするかどうか
  2. リサイクルにはどのくらい時間がかかりますか

最初の問題に対応して、スレッドプールは一時スレッドをリサイクルする必要があることを規定しています。コアスレッドは、リサイクルするかどうかを決定するためにユーザーが設定したallowCoreThreadTimeOut値に基づいています。
2番目の質問では、コアスレッドと一時スレッドの回復時間は同じであり、keepAliveTime +単位によって決定されます。

3.実装メカニズム

スレッドプールは複雑に見えるかもしれませんが、内部は非常に単純です。上記のスレッドプールの動作原理を理解し、その原理でソースコードを理解している限り、それはなじみのある道のようです。
最初に内部メンバー変数を見て、それらを通常の変数と特別な変数に分けます。

変数

従来の変数:

private final BlockingQueue<Runnable> workQueue; 				// 任务队列
private final ReentrantLock mainLock = new ReentrantLock();		// 线程池的主锁
private final Condition termination = mainLock.newCondition();	// 配合线程池主锁的释放器
private final HashSet<Worker> workers = new HashSet<Worker>();	// 线程的存储容器 
private int largestPoolSize;									// 最大线程数量
private long completedTaskCount;								// 线程池以及执行完成的任务个数
private volatile ThreadFactory threadFactory;					// 线程池的线程工厂
private volatile RejectedExecutionHandler handler;				// 拒绝策略
private volatile long keepAliveTime;							// 线程空闲后的回收时间限制
private volatile boolean allowCoreThreadTimeOut; 				// 是否允许核心线程超时后进行回收
private volatile int corePoolSize;								// 线程池的核心线程数量
private volatile int maximumPoolSize;							// 线程池的最大线程数量
private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();// 默认的拒绝策略

特殊変数

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); // 一个原子类的CAS操作
private static final int COUNT_BITS = Integer.SIZE - 3;					// COUNT_BITS 为29,表示int类型二进制中,其中29位用来表示线程池中线程的最大运行数量,剩余3位用来表示线程池状态。
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;			// 线程池中的线程最大容量
private static final int RUNNING    = -1 << COUNT_BITS;		// 运行态,二进制111 00000000000000000000000000000
private static final int SHUTDOWN   =  0 << COUNT_BITS;		// 关闭态,二进制000 00000000000000000000000000000
private static final int STOP       =  1 << COUNT_BITS;		// 停止态,二进制001 00000000000000000000000000000
private static final int TIDYING    =  2 << COUNT_BITS;  	// 整理态,二进制010 00000000000000000000000000000
private static final int TERMINATED =  3 << COUNT_BITS; 	// 结束态,二进制011 00000000000000000000000000000 

特殊変数の設計思想については、上からいくつかの手がかりが見られたかもしれません。

  1. 設計者は、int型の値を使用して、スレッドプールの現在の実行状態と実行中のスレッドの数を表したいと考えています。int型の32ビットバイトのうち、最初の3ビットはステータスを示すために使用され、最後の29ビットは数量を示すために使用されます。
  2. ctl変数はそのような統合されたアトミック変数であり、
    プライベートファイナルAtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING、0));
    から、スレッドプールが作成された後、デフォルトはRUNNING状態であり、その数はスレッドの数は0です。これは、上記の原則で、スレッドプールが作成された直後にスレッドプールが作成されない理由も説明していますが、その理由は空のスレッドプールです。
  3. 状態値の設計の観点から、RUNNING状態は最小であり、それよりも大きい状態は非RUNNING状態です。以下のすべてのソースコードの一部は、RUNNING状態とnon-RUNNING状態で判断されます。

コンストラクタ

コンストラクターは単に値を変数に割り当てるだけで、いくつかのオーバーロードがあります。

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.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

労働者(インナークラス)

これはスレッドプールの中核であり、ThreadとRunnableを統合するワーカーとして理解しています。
したがって、取得したスレッドを使用して、受信したタスクを実行できます。

final Thread thread;
Runnable firstTask;
volatile long completedTasks;

ワーカーの構築にはタスクが必要であるため、このタスクは実行する必要のある最初のタスクです。

Worker(Runnable firstTask) {
        setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);		// 利用线程工厂创建。
    }

タスクがあり、スレッドがあります。次に、ワーカーはスレッドを使用してタスクを実行できますが、どのように実行しますか?

public void run() {
        runWorker(this);
    }
    
官方注释:
主工作程序运行循环。从队列中反复获取任务并执行它们,同时处理一些问题:
1. 我们可以从一个初始任务开始,在这种情况下,我们不需要获得第一个任务。否则,只要池在运行,我们就会从getTask获得任务。如果返回null,则工作程序将由于池状态或配置参数的更改而退出。其他退出是由于抛出外部代码中的异常导致的,在这种情况下complete突然保持,这通常会导致processWorkerExit替换此线程。
2.在运行任何任务之前,会获得锁,以防止任务执行时其他池中断,然后我们确保除非池停止,否则这个线程不会设置它的中断。
3.在每个任务运行之前,都会调用beforeExecute,这可能会抛出一个异常,在这种情况下,我们会导致线程在不处理任务的情况下死亡(用completedsuddenly true中断循环)。
4.假设beforeExecute正常完成,我们运行任务,收集它抛出的任何异常发送到afterExecute。我们分别处理RuntimeException、Error(规范保证会捕获这两者)和任意可抛掷事件。因为我们不能在Runnable.run中重新抛出可抛弃物,所以我们在抛出时将它们包装在错误中(到线程的UncaughtExceptionHandler中)。保守地说,抛出的任何异常都会导致线程死亡。
5.在task.run完成后,我们调用afterExecute,这也会抛出一个异常,这也会导致线程死亡。根据JLS第14.20节,即使task.run抛出,这个异常也会生效。异常机制的最终效果是,在执行后,线程的UncaughtExceptionHandler提供了我们所能提供的关于用户代码遇到的任何问题的同样准确的信息。

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        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
            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);
    }
}


詳細なソースコードは分析されませんので、後で戻ってきます。
ここでは、メインAPIを分析します。

メソッドを実行する

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    /*
     * 分3个步骤进行:
     *
     * 1.如果运行的线程少于corePoolSize,则尝试使用给定命令作为其
     * 第一个任务启动一个新线程。对addWorker的调用会自动检查runState和
     * workerCount,从而通过返回false防止在不应该添加线程的情况下添加线程的错误警报。
     *
     * 2.如果任务可以成功排队,那么我们仍然需要再次检查是否应该添加一个线程
     * (因为现有的线程在上次检查后死亡),或者池在进入此方法后关闭。因此,我们会
     * 重新检查状态,如果停止队列,必要时回滚队列;如果没有线程,则启动一个新线程。
     *
     * 3. 如果它失败了,我们知道我们被关闭或饱和,因此拒绝这个任务。
     */
    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);
}

送信方法

ThreadPoolExecutorのsubmitメソッドは、親クラスから継承されます。

public <T> Future<T> submit(Callable<T> task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);
    return ftask;
}

内部から、submitが呼び出されるexecuteメソッドであることがわかります。executeとの違いは、submitがFutureを返すことです。タスクは、Futureを介して操作できます。

送信と実行の違い

おすすめ

転載: blog.csdn.net/weixin_43901067/article/details/108033415