[JUCソースコード]スレッドプール:タスク実行プロセスのThreadPoolExecutor(4)ソースコード分析

スレッドプールシリーズ:

1.execute()

エントランス、実行戦略を選択し、次の3つの状況に分けます。

  • ケース1:ワーカースレッド<コアの数、タスクを実行するためのスレッドを作成する
  • ケース2:ワーカースレッド> =コアの数であり、タスクキューがいっぱいではない場合、タスクキューに参加します(コアスレッドが実行されるのを待機します)
    • スレッドプールが異常です。現在のタスクを削除してください
    • 制限状況:使用可能なスレッドはエンキュー時にリサイクルされるだけで、タスクのない新しいスレッドが作成されます
  • 状況3:タスクキューがいっぱいです
    • キューがいっぱいです&&スレッド数<maxSize:タスクを処理するための新しいスレッドを作成します
    • キューがいっぱいです&&スレッド数> = maxSize:RejectedExecutionHandlerクラスを使用してリクエストを拒否します
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;
        // 由于 addWorker -> runWorker -> getTask,所以线程池状态可能发生变化
        c = ctl.get();
    }
    // 情况二:工作的线程大于等于核心线程数且任务队列没满
    // 注:isRunning是校验线程池状态是否正常。另外,offer不阻塞而是返回t/f
    if (isRunning(c) && workQueue.offer(command)) {
    
    
        int recheck = ctl.get();
        // 如果线程池状态异常 尝试从队列中移除任务,可以移除的话就拒绝掉任务
        if (!isRunning(recheck) && remove(command))
            reject(command);
        // 发现可运行的线程数是 0,就初始化一个线程,这里是个极限情况,入队的时候,突然发现可用线程都被回收了
        else if (workerCountOf(recheck) == 0)
            // Runnable是空的,不会影响新增线程,但是线程在 start 的时候不会运行
            // Thread.run() 里面有判断
            addWorker(null, false);
    }
    // 情况三:队列满了,开启线程到 maxSize,如果失败直接拒绝(这段逻辑可以在addWorker方法中看到)
    else if (!addWorker(command, false))
        reject(command);
}

2.addWorker()

ワーカーを作成し、ワーカーが正常に開始されたかどうかを返します。一般的なプロセスは次のとおりです。

  1. スレッドプールステータスの検証
    • 失敗した場合はfalseを返します。2つの理由があります:
      • 異常なスレッドプールステータス:SHUTDOWN、STOP、TIDYING、TERMINALED
      • ワーカースレッド数のオーバーフロー:スレッド数> =容量、またはcoreThreadを使用する場合は、スレッド数> = coreSizeまたはmaxSize
    • 成功:CASはworkCountを1つ増やします
  2. ワーカーを作成する
    1. 2つの識別変数を作成します:workerAdded、workerStarted
    2. ワーカーを構築します。構築中にnewThreadメソッドを使用して新しいスレッドが作成されます
    3. ロックし、新しく作成したワーカーを、ワーカーを管理するコンテナー(セット)に追加します。ロックは同時実行中のスレッドセーフを保証します
  3. ワーカーでスレッドを開始します。呼び出しロジックは次のとおりです。Thread#start()-> Worker#run()-> runWorker()
// firstTask 不为空可以直接执行,为空执行不了,Thread.run()方法有判断,Runnable为空不执行
// core 为 true 表示线程最大新增个数是 coresize,false 表示最大新增个数是 maxsize
private boolean addWorker(Runnable firstTask, boolean core) {
    
    
    
	// break retry 跳到retry处,且不再进入循环
	// continue retry 跳到retry处,且再次进入循环
    retry:
--------------------------------------------------------------------------------------------------------------    
    // 1.先是各种状态的校验
    for (;;) {
    
    
        int c = ctl.get();
        int rs = runStateOf(c); // 获取线程池状态
        
        // 1.1 校验线程池状态,rs>=0:SHUTDOWN,STOP,TIDYING,TERMINALED
        if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())) 
            return false;

        for (;;) {
    
    
            int wc = workerCountOf(c); // 得到当前工作线程数,即worker数
            // 1.2 校验工作中的线程数大于等于容量,或者大于等于 coreSize or maxSize
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize)) // 如果core为true就判断是否大于coreSize,否则判断maxSize
                return false;
            // CAS修改 workerCount(+1)
            if (compareAndIncrementWorkerCount(c))
                // break 结束 retry 的 for 循环
                break retry;
            // 到这里可能是CAS失败了,重新获取 ctl
            c = ctl.get();  
            // 如果线程池状态被更改
            if (runStateOf(c) != rs)
                continue retry; // 跳转到retry位置,重新判断
            // else CAS failed due to workerCount change; retry inner loop
        }
    }
--------------------------------------------------------------------------------------------------------------
	// 2.创建worker
	// 2.1 创建标识变量
    boolean workerStarted = false; // woker启动标识
    boolean workerAdded = false;  // woker成功加入worker容器标识
    Worker w = null;
    try {
    
    
    	// 2.2 构造worker。在worker的构造函数中会调用newThread方法创建一个Thread
    	// 注:由于Worker也实现了Runnable,所以在创建线程的时候是newThread(this)。这是一个巧妙的设计
        w = new Worker(firstTask);
        final Thread t = w.thread; // 获取worker中的线程
        // 2.3 将worker加入到worker容器(Set)
        if (t != null) {
    
    
            final ReentrantLock mainLock = this.mainLock; // 这个mainLock是一个成员变量,作用是控制对worker的操作
            // 加锁是因为,可能有多个线程同时要将worker放入worker容器
            mainLock.lock();
            try {
    
    
				// 获取到线程池状态rs
                int rs = runStateOf(ctl.get());
                if (rs < SHUTDOWN || // 如果线程池状态是 RUNNING
                    (rs == SHUTDOWN && firstTask == null)) {
    
     // 线程池是SHUTDOWN且要执行的任务为null
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    // 将当前woker加入到 HashSet<Worker> workers 中
                    workers.add(w); 
                    int s = workers.size(); // 获取到 workers 的大小,即现在有几个worker
                    // 如果worker数已经大于了最大线程池容量
                    if (s > largestPoolSize) 
                        largestPoolSize = s; // 将largestPoolSize设置为worker现在的书香
                    workerAdded = true; // 添加标志设置为成功
                }
            } finally {
    
    
                mainLock.unlock(); // 解锁
            }
--------------------------------------------------------------------------------------------------------------            
            // 3.启动如果woker中的线程。前提是worker已经添加成功
            if (workerAdded) {
    
    
                // 启动刚创建线程:Thread#start -> Worker#run -> runWorker
                t.start();
                workerStarted = true; // 线程启动标志置为true
            }
        }
    } finally {
    
    
    	// 如果线程启动失败
        if (! workerStarted) 
            addWorkerFailed(w);
    }
    // 返回线程是否启动成功
    return workerStarted;
}

3.runWorker()

最初にタスクを取得してから、ワーカーにタスクを実行させます。このメソッドの一般的なロジックは次のとおりです。

  1. タスクを取得するには2つの方法があります
    • firstTask:ワーカーの初期タスク
    • getTask():タスクキューのタスク
  2. 実行中にスレッドがタスクにスローされないようにロックする
  3. スレッドプールが停止している場合は、現在のスレッドを中断します
  4. フック機能の前に実行
  5. タスクを実行します。つまり、task.run()を呼び出します。
  6. アフターフック機能を実行する
  7. 現在のタスクを削除し、ロックを解除します。次のタスクを実行している間

ここでもう1つ注意してください。目的は、現在のスレッドを維持してタスクを実行し続けることですが、スレッドがタスクの取得に失敗した場合(getTaskメソッドはブロックして待機します)、ループ、つまりスレッドを終了します。寿命が尽きたときにリサイクルされます。

final void runWorker(Worker w) {
    
    
    Thread wt = Thread.currentThread(); // 获取当前线程
    Runnable task = w.firstTask;  // 尝试获取创建worker时的firstTask
    
    w.firstTask = null; // 从这可以看出,只要firstTask执行过一次,就会一直被置为null
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
    
    
    	// 1.获取任务:如果firstTask已经被执行过了,就从任务队列中获取
    	// 注:通过while维持了线程的存活,并不断获取任务取执行。若迟迟拿不到任务,就会退出while结束线程
        while (task != null || (task = getTask()) != null) {
    
    
            // 2.锁住 worker,防止worker在执行任务时被丢入另一个任务
            w.lock();
            // 3.判断线程池若处于 stop 中,但线程没有到达中断状态,帮助线程中断
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
    
    
                // 4.执行 before 钩子函数
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
    
    
                    // 5.同步执行任务
                    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 {
    
    
                    // 6.执行 after 钩子函数
                    // 如果这里抛出异常,会覆盖 catch 的异常,所以这里异常最好不要抛出来
                    afterExecute(task, thrown);
                }
            } finally {
    
    
                // 7.任务执行完成,删除任务,并计算解锁
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
    
    
        // 做一些抛出异常的善后工作
        processWorkerExit(w, completedAbruptly);
    }
}

4.getTask()

ブロッキングキューからタスクを取得します。ブロックして待機した後にタスクが取得されない場合、タスクはnullを返します。そのため、現在のスレッドはrunWorkerメソッドのwhileループを終了し、リサイクルされます。これは、実行することが残っておらず、リソースが無駄になりました。具体的なリサイクル戦略はソースコードにあり、メソッドの一般的なプロセスは次のとおりです。

  1. 現在のスレッドをリサイクルする最初の判断:スレッドプールSHUTDOWN、およびキューは空です
  2. 2番目の判断は、現在のスレッドをリサイクルし、次の条件のいずれかを満たすことです。
    1. wc> maximumPoolSize && wc> 1:既存のワーカーの数がスレッドプールの最大容量を超えており、リサイクル後にスレッドプールに少なくとも1つのスレッドがあります
    2. wc> maximumPoolSize && workQueue.isEmpty():既存のワーカーの数がスレッドプールの最大容量を超えており、タスクキューが空です
    3. timed && timedOut && wc> 1:コアスレッドのリサイクルを許可するか、既存のスレッドの数がコアの数を超え、現在のスレッドがタイムアウトし、リサイクル後にスレッドプールに少なくとも1つのスレッドがあります
    4. timed && timedOut && workQueue.isEmpty():コアスレッドのリサイクルを許可するか、既存のスレッドの数がコアの数を超え、現在のスレッドがタイムアウトになり、タスクキューが空になります
  3. タスクキューからタスク(取得またはポーリング)を取得し、取得した場合は戻り、取得しなかった場合はタイムアウト(timedOut)をtrueに設定します。
    注:timedがtrueの場合にのみ、ポーリングが使用されて待機します。 KeepAliveTimeの場合、それ以外の場合は、常に時間がかかります。それを待ちます

PS:ここでも、コアスレッドと非コアスレッドは概念的にのみ異なります。コード内のすべての人は同じであり、すべて通常のスレッドです。

private Runnable getTask() {
    
    
	// 标识是否超时
	// 默认false,但如果下面自旋中 poll 在 keepAliveTime(线程存活时间) 没等到任务,就会将timedOut置为true
    boolean timedOut = false; // Did the last poll() time out?

    for (;;) {
    
    
        int c = ctl.get();
        int rs = runStateOf(c); // 获取线程池状态
        
		// 1.第一次判断是否回当前收线程
        // 线程池关闭 && 队列为空,不需要在运行了,直接返回null
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
    
    
            decrementWorkerCount(); // workerCount--
            return null;
        }

        int wc = workerCountOf(c); // 获取worker个数
        // timed的作用是决定在阻塞队列中等任务时用 poll 还是 take
        // timed = 核心线程可以被灭亡(默认false) || 运行的线程数大于 coreSize 
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
	   
        // 2. 第二次判断是否回收当前线程。组合后分为4种情况
        if ((wc > maximumPoolSize || (timed && timedOut))  // woker大于线程池最大数量 || (timed && 当前线程已经超时)
            && (wc > 1 || workQueue.isEmpty())) {
    
     // woker大于1 || 任务队列为空
            // 通过CAS使workerCount--
            if (compareAndDecrementWorkerCount(c)) 
                return null;
            continue;
        }

        try {
    
    
        	// 3.从阻塞队列中获取任务。timed 决定了是使用 poll 还是 take
        	// keepAliveTime 是线程最大空闲时间,是构造线程池的入参
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : // poll,超时了就返回
                workQueue.take(); // take,任务队列中没任务会阻塞等待
            // 如果在队列拿到了任务就返回
            if (r != null)
                return r;
            // 没拿到就将超时timedOut设置为true,表示此时队列没有数据
            timedOut = true;
        } catch (InterruptedException retry) {
    
    
            timedOut = false;
        }
    }
}

概要

ここに画像の説明を挿入

おすすめ

転載: blog.csdn.net/weixin_43935927/article/details/113965324