スレッド プール ThreadPoolExecutor のソース コード分析

スレッド プール ThreadPoolExecutor のソース コード分析

概要

  1. スレッドの作成方法
  2. スレッドプールパラメータ
  3. スレッドプールの実行フローチャート
  4. 実行プロセスのソースコード解析
  5. スレッドプールのステータス図
  6. ワーカーのカプセル化
  7. スレッド実行の後続処理
  8. スレッド プール内のスレッド実行タスクの全体的なワークフロー

1. スレッドの立て方

  1. Threadクラスを継承する

ここに画像の説明を挿入します

  1. 実行可能なインターフェースを実装する

    [外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムが存在する可能性があります。画像を保存して直接アップロードすることをお勧めします (img-UrdyouiC-1691579008075) (C:\Users\hejh\AppData\Roaming\Typora\) typora-user-images\ image-20230809160842904.png)]

  2. 呼び出し可能なインターフェースを実装し、戻り値を受け取る

    [外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムが存在する可能性があります。画像を保存して直接アップロードすることをお勧めします (img-l497CyhI-1691579008077) (C:\Users\hejh\AppData\Roaming\Typora\) typora-user-images\ image-20230809161233187.png)]

  3. スレッド プールをカスタマイズするか、JUC パッケージの下に記述されたスレッド プールを使用します。スレッド プールの方法は実際には上記と変わりませんが、上記ではスレッドの頻繁な作成と破棄が必要となり、不必要な追加のリソース消費が発生する点が異なります。 , ということで、実際の開発では Javaでは必ずスレッドプールメソッドが使われることになりますが、JavaのExecutorにはスレッドプールを作成するメソッドがいくつか用意されています。

    Executors クラスの下で:

    [外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムが存在する可能性があります。画像を保存して直接アップロードすることをお勧めします (img-QLJADNcf-1691579008078) (C:\Users\hejh\AppData\Roaming\Typora\) typora-user-images\ image-20230809164946394.png)]

2. スレッドプールパラメータ

java.util.concurrent.ThreadPoolExecutor は、カスタム スレッド プールのクラスです。

 /**
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the {@code Runnable}
     *        tasks submitted by the {@code execute} method.
     * @param threadFactory the factory to use when the executor
     *        creates a new thread
     * @param handler the handler to use when execution is blocked
     *        because the thread bounds and queue capacities are reached
     * @throws IllegalArgumentException if one of the following holds:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@code maximumPoolSize < corePoolSize}
     * @throws NullPointerException if {@code workQueue}
     *         or {@code threadFactory} or {@code handler} is null
     */
    public ThreadPoolExecutor(int corePoolSize, //核心线程数
                              int maximumPoolSize, //最大线程数
                              long keepAliveTime, //空闲线程存活时间
                              TimeUnit unit, //时间单位
                              BlockingQueue<Runnable> workQueue, //阻塞队列
                              ThreadFactory threadFactory, //线程工厂
                              RejectedExecutionHandler handler) // 拒绝策略
  • corePoolSizeスレッド プールのコア スレッド サイズは、スレッド プール内の最小数のスレッドを維持します。これらのスレッドがアイドル状態を処理する場合でも、allowCoreThreadTimeOut が設定されない限り、スレッドは破棄されません。ここでのスレッドの最小数は corePoolSize です。

  • MaximumPoolSizeスレッド プール内のスレッドの最大数です。タスクがスレッド プールに送信された後、タスクはまずアイドル状態で残っているスレッドがあるかどうかを調べます。存在する場合、タスクはアイドル状態のスレッドに直接渡されます。そうでない場合はワークキューにキャッシュされます(後で紹介します))、ワークキューがいっぱいの場合は新しいスレッドが作成され、ワー​​クキューの先頭からタスクが取り出され、処理のために新しいスレッドに渡され、新しく送信されたタスクは作業キューの最後尾に配置されます。スレッド プールは無制限に新しいスレッドを作成するのではなく、maximumPoolSize で指定されるスレッドの最大数に制限があります。

  • keepAliveTimeアイドル状態のスレッドの生存時間。スレッドがアイドル状態で、現在のスレッド数が corePoolSize より大きい場合、アイドル状態のスレッドは指定された時間が経過すると破棄されます。ここでの指定時間は keepAliveTime によって設定されます。

  • アイドルスレッド生存時間の測定単位 単位 keepAliveTime

  • workQueue作業キュー内の新しいタスクが送信されると、最初に作業キューに入り、その後、タスクのスケジュール設定中にタスクがキューから削除されます。jdk には 4 つのワーク キューが提供されています。

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

    2. LinkedBlockingQueue は、FIFO に従ってソートされたリンク リスト (実際の最大容量は Interger.MAX) に基づく無制限のブロッキング キューです。キューのほぼ無制限の性質により、スレッド プール内のスレッドの数が corePoolSize に達すると、新しいタスクが入ってきて、maxPoolSize まで新しいスレッドを作成せずにキューに格納されます。そのため、このワーク キューを使用する場合、パラメータ maxPoolSize 実際には機能しません。

    3. SynchronousQueue はタスクをキャッシュしないブロッキングキューで、プロデューサはタスクを投入し、コンシューマがタスクを取り出すまで待たなければなりません。つまり、新しいタスクが到着すると、キャッシュされませんが、タスクの実行が直接スケジュールされます。使用可能なスレッドがない場合は、新しいスレッドが作成されます。スレッドの数が maxPoolSize に達すると、拒否ポリシーが実行されます。4. PriorityBlockingQueue は、優先度を持つ無制限のブロッキング キューであり、優先度はパラメータ Comparator によって実装されます。

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

  • ハンドラー拒否ポリシー ワークキュー内のタスクが最大制限に達し、スレッド プール内のスレッド数も最大制限に達した場合に、新しいタスクが送信された場合の処理​​方法。ここでの拒否戦略は、この問題を解決するためのものであり、JDK には 4 つの拒否戦略が用意されています。

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムが存在する可能性があります。画像を保存して直接アップロードすることをお勧めします (img-rJeEfDbi-1691579008079) (C:\Users\hejh\AppData\Roaming\Typora\) typora-user-images\ image-20230809165906985.png)]

3. スレッドプール実行フローチャート

4. 実行処理のソースコード解析

PS: 次のメソッドはすべて java.util.concurrent.ThreadPoolExecutor クラスの下にあります。

1. 特性

    //原子类,用于保存线程池的状态和工作线程的数量。总共32位,前3位表示线程池的状态,后29位表示工作线程的数量 
    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;

    // 线程池的状态,保存在前三位
    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;

    // 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; }

2.メソッドの実行

public void execute(Runnable command) {
    
    
        //健壮性判断
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        //如果工作线程的数量小于核心线程数,则创建线程处理任务,addWorker之前都要检查线程池状态  
        if (workerCountOf(c) < corePoolSize) {
    
    
            if (addWorker(command, true))
                return;
            //防止多线程情况下c变成其他值
            c = ctl.get();
        }
        //核心线程已满,如果线程池处于运行状态,将任务放进阻塞队列
        if (isRunning(c) && workQueue.offer(command)) {
    
    
            int recheck = ctl.get();
            //重复检查,如果不是运行状态,移除队列中的任务,拒绝任务。addWorker之前都要检查线程池状态
            if (! isRunning(recheck) && remove(command))
                reject(command);
            //如果工作线程为0,就添加一个线程,避免出现队列任务没有线程执行的情况。
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
       //添加线程处理这个任务,如果失败则拒绝。
        else if (!addWorker(command, false))
            reject(command);
    }

3.addWorkerメソッド

private boolean addWorker(Runnable firstTask, boolean core) {
    
    
        retry:
        for (;;) {
    
    
            int c = ctl.get();
            int rs = runStateOf(c);

            // 仅在必要时检查队列是否为空。
            if (rs >= SHUTDOWN && // 线程池属于关闭状态,无法接受新任务,也无法处理池中任务,
                ! (rs == SHUTDOWN && // !SHUTDOWN,即是STOP,TIDYING,TERMINATED
                   firstTask == null && // firstTask不为空 -> 这里对应上述的addWorker(null,false)
                   ! workQueue.isEmpty()))// 工作队列为空
                return false;

            for (;;) {
    
    
                int wc = workerCountOf(c);
                //工作线程数再判断
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                //通过CAS操作增加工作线程数
                if (compareAndIncrementWorkerCount(c))
                    break retry; 
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
                // CAS 由于workerCount 变化而失败;重试内循环
            }
        }
        //开始添加工作线程
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
    
    
            w = new Worker(firstTask);
            final Thread t = w.thread;
            //判断t!=null的目的,防止t创建失败
            if (t != null) {
    
    
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();//加锁的原因就是largestPoolSize,
                try {
    
    
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int rs = runStateOf(ctl.get());

                    if (rs < SHUTDOWN ||//线程池运行中
                        (rs == SHUTDOWN && firstTask == null)) {
    
    //处于shutdown状态,firstTask == null这里对应上述的addWorker(null,false)
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        workers.add(w);//workers:线程池中所有工作线程的集合,
                        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;
    }

4. addWorkerFailed メソッド、スレッド失敗メソッドを追加

//通过上面的代码,总结哪些情况会出现添加失败的情况:
1,线程池不在运行状态
2,线程已经启动    
这2种情况都没有添加成功    workers.remove(w);不会有问题。
private void addWorkerFailed(Worker w) {
    
    
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();//因为decrementWorkerCount而加锁
        try {
    
    
            if (w != null)
                workers.remove(w);
            decrementWorkerCount();
            //重新检查线程池终止情况,以防此线程存在影响线程池终止。
            tryTerminate();
        } finally {
    
    
            mainLock.unlock();
        }
    }

5. スレッドプールの状態図

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムが存在する可能性があります。画像を保存して直接アップロードすることをお勧めします (img-SnaGuPtK-1691579008080) (C:\Users\hejh\AppData\Roaming\Typora\) typora-user-images\ image-20230809180334809.png)]

6. 作業者の梱包

private final class Worker extends AbstractQueuedSynchronizer implements Runnable
    {
    
    
        final Thread thread;
        /** Initial task to run.  Possibly null. */
        Runnable firstTask;
        /** Per-thread task counter */
        volatile long completedTasks;

        Worker(Runnable firstTask) {
    
    
            setState(-1); // 添加标识,worker运行前,禁止中断(AQS)
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

        /** 具体运行调用外部(threadPoolExecutor)的方法  */
        public void run() {
    
    
            runWorker(this);
        }

        // Lock methods AQS的状态
        //
        // The value 0 represents the unlocked state.
        // The value 1 represents the locked state.

        protected boolean isHeldExclusively() {
    
    
            return getState() != 0;
        }

        protected boolean tryAcquire(int unused) {
    
    
            if (compareAndSetState(0, 1)) {
    
    
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        protected boolean tryRelease(int unused) {
    
    
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        public void lock()        {
    
     acquire(1); }
        public boolean tryLock()  {
    
     return tryAcquire(1); }
        public void unlock()      {
    
     release(1); }
        public boolean isLocked() {
    
     return isHeldExclusively(); }

        void interruptIfStarted() {
    
    
            Thread t;
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
    
    
                try {
    
    
                    t.interrupt();
                } catch (SecurityException ignore) {
    
    
                }
            }
        }
    }

java.util.concurrent.ThreadPoolExecutor#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 {
    
    
            //任务不为null,就一直循环,否则调用getTask尝试从阻塞队列获取任务
            while (task != null || (task = getTask()) != null) {
    
    
                w.lock();// 加锁的目的是表示当前任务正在执行,你shutdown任务也不会中断
                // 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);
        }
    }

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);
            // Are workers subject to culling?
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
            //
            if ((wc > maximumPoolSize || (timed && timedOut))//当前工作线程数大于最大线程数 ,后面判断表示是否是允许核心线程超时并且真的超时
                && (wc > 1 || workQueue.isEmpty())) {
    
    //工作线程 > 1或者 阻塞队列为空
                if (compareAndDecrementWorkerCount(c))// 干掉当前工作线程并返回null,CAS的方式,如果失败,重新走一遍
                    return null;
                continue;
            }

            try {
    
    
                Runnable r = timed ?
                    // 这里是可能出现超时情况并且允许回收线程,那就阻塞这么久拿阻塞队列的任务
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                //这里是线程阻塞在这,等待任务,不参与回收的情况,直到触发signal方法被唤醒,走catch继续下次循环
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
    
    
                timedOut = false;
            }
        }
    }

7. スレッド実行以降の処理

processWorkerExit方法:

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    
    
        if (completedAbruptly) // 如果停止,减一个工作线程数
            decrementWorkerCount();

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();//加锁为了移除工作线程workers.remove(w);
        try {
    
    
            completedTaskCount += w.completedTasks;
            workers.remove(w);
        } finally {
    
    
            mainLock.unlock();
        }

        tryTerminate();// 尝试干掉线程池

        int c = ctl.get();
        if (runStateLessThan(c, STOP)) {
    
    
            // 如果不是认为停止,需要判断线程是否需要追加一个线程处理任务
            if (!completedAbruptly) {
    
    
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;// 查看核心线程是否允许超时
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;// 如果允许超时,并且工作队列不是空,就将min设置为1
                if (workerCountOf(c) >= min)// 如果工作线程数量大于核心线程数,就直接结束
                    return; // replacement not needed
            }
            addWorker(null, false);
        }
    }

8. スレッド プール内のスレッド実行タスクの全体的なワークフロー

ここに画像の説明を挿入します

大まかなプロセスの説明:

  1. タスクの実行時に、コア スレッドの数が corePoolSize の値に達しない場合、スレッドが作成されます。それ以外の場合は、タスクキューに入れられます。
  2. タスク キューがいっぱいになると、スレッドが作成されますが、スレッドの総数は、maximumPoolSize 値を超えることはできません。
  3. タスク キューがいっぱいで、スレッド数が MaximumPoolSize 値に達すると、失敗ポリシーが実行されます。
  4. ワーカー スレッドはキュー内のポーリング タスクを継続的にポーリングします。ポーリングが空の場合、ワーカー スレッドの実行は終了します (スレッドのリサイクル)。
  5. ワーカー スレッドの数 <= コア スレッドの数 corePoolSize の場合、take を使用してキューからタスクを取得します (コア スレッドは常に待機します)。

まず、getTaskは無限forループに入っており、現在実行中のスレッド数≦コアスレッド数と判断し、ブロッキングキューのtakeメソッドを直接呼び出し、データが入るまで無限にブロックします。キューを終了し、for ループを終了するためにウェイクアップします。

現在実行中のスレッドの数 > コア スレッドの数の場合、キューにデータがない場合は、戻る前に keepAliveTime をブロックします。for ループを続行するとき、キューが空の場合は、for ループを終了してスレッドを破棄します。キュー上でブロックされています。

スレッドプールプロセスの概要

タスクが送信されたときに、現在実行中のスレッドの数がコア スレッドの数より少ない場合は、ワーカーを構築し、ワーカーの start を呼び出してスレッドを開始します。スレッドの実行メソッドは while ループです。ループ内では、グローバル ブロッキング キューからデータを取得するために getTask が呼び出されます。データは取得後すぐに実行され、getTask メソッドのキューでブロックされます。

タスクがサブミットされたとき、現在実行中のスレッドの数がコア スレッドの数以上でキューがいっぱいでない場合、そのタスクはグローバル ブロッキング キューに入れられます。このとき、上記の getTask によってブロックされたスレッドはビジネスタスクを実行するために起動します。

タスクが送信されるときに、現在実行中のスレッドの数がコア スレッドの数以上でキューがいっぱいの場合、現在実行中のスレッドの数が最大スレッド数を超えているかどうかの判断が継続されます。スレッドの最大数を超えると、タスク拒否ポリシーが使用され、それ以外の場合はタスクが構築されます。worker は、ワーカーの start を呼び出してスレッドを開始し、while ループの getTask 操作の最初のステップを実行します。

getTask オペレーション

getTask の場合、現在実行中のスレッドの数がコア スレッドの数以内で、スレッドが緊張状態にない場合、データがキューに入れられてから実行に戻るまで、スレッドはキュー内で無期限にブロックされます。現在実行中のスレッドがコア スレッドの数より大きい場合、スレッドが開始されます。keepAliveTime はブロックされます。この期間中にキューからデータが送信されない場合、ワーカー スレッドは破棄され、リサイクルが許可されます。

KeepAliveTime の役割は何ですか?

keepAliveTime (スレッド アクティビティ保持時間): スレッド プールのワーカー スレッドがアイドル状態になった後、アクティブな状態を維持する時間。このパラメータは、スレッド数が corePoolSize より大きい場合にのみ役立ちます。この時間を超えるアイドル スレッドは終了します。ただし、allowCoreThreadTimeOut が true に設定されている場合、keepAliveTime までタスクの実行がない場合、コア スレッド数も破棄されます。時間。タスクの数が多く、各タスクの実行時間が比較的短い場合は、この時間を増やしてスレッドの使用率を向上させることができます。

おすすめ

転載: blog.csdn.net/Edward_hjh/article/details/132194867
おすすめ