ThreadPoolExecutor源码分析笔记 —— 挺全的

一、从构造方法开始

二、exucute()

scss 复制代码
public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    /*
     * Proceed in 3 steps:
     *
     * 1. If fewer than corePoolSize threads are running, try to
     * start a new thread with the given command as its first
     * task.  The call to addWorker atomically checks runState and
     * workerCount, and so prevents false alarms that would add
     * threads when it shouldn't, by returning false.
     *
     * 2. If a task can be successfully queued, then we still need
     * to double-check whether we should have added a thread
     * (because existing ones died since last checking) or that
     * the pool shut down since entry into this method. So we
     * recheck state and if necessary roll back the enqueuing if
     * stopped, or start a new thread if there are none.
     *
     * 3. If we cannot queue task, then we try to add a new
     * thread.  If it fails, we know we are shut down or saturated
     * and so reject the task.
     */
    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)  //如果此时线程数量已经是0,创建工作线程
            addWorker(null, false);
    }
    else if (!addWorker(command, false)) //如果放入阻塞队列失败
        reject(command);
}
  • RUNNING: -1 << COUNT_BITS,即高3位为111,该状态的线程池会接收新任务,并处理阻塞队列中的任务;
  • SHUTDOWN: 0 << COUNT_BITS,即高3位为000,该状态的线程池不会接收新任务,但会处理阻塞队列中的任务;
  • STOP : 1 << COUNT_BITS,即高3位为001,该状态的线程不会接收新任务,也不会处理阻塞队列中的任务,而且会中断正在运行的任务;
  • TIDYING : 2 << COUNT_BITS,即高3位为010, 所有的任务都已经终止;
  • TERMINATED: 3 << COUNT_BITS,即高3位为011, terminated()方法已经执行完成

ctl中储存了线程池的状态和线程数,低29位储存线程数量,高3位储存线程状态。初始时,低三位为RUNNING,线程数是0。当需要获取线程池信息时,通过按位与运算(&)即可取出状态或线程数量,这里的ctl.get()实际上是先获取了线程池32位信息。

workerCountOf(c)方法即为从ctl中取出线程的数量

如果当前线程数量小于线程池核心数,进入addWorker(),参数core为true时线程池数量最大即为corePoolSize,这里超过不会创建新线程(救急线程)

三、addWorker()

ini 复制代码
private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c); //获取运行状态

        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN &&   //无任务或运行状态为非SHUTDOWN的结束态时返回false,如果在SHUTDOWN状态且仍有任务会执行完剩余任务
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;

        for (;;) {
            int wc = workerCountOf(c); //获取线程数量
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize)) //core为true时大于corePoolSize即不再创建新线程,无救济线程
                return false;
            if (compareAndIncrementWorkerCount(c)) //如果满足创建新线程条件则线程池数量先加1
                break retry;
            c = ctl.get();  // Re-read ctl
            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 {
        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());  //再次重读状态

                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {   //再次检查运行状态
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    workers.add(w);  //将线程加入工作集合,这是一个HashSet
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;  //记录最大线程池中线程数,可用于观测线程池运行情况
                    workerAdded = true;  //加入成功的标识
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {  //如果添加成功,start线程
                t.start(); 
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);  //如果线程启动失败,这里会从workers中移除添加的worker,并且尝试终止线程池
    }
    return workerStarted; //返回值即可看作线程是否创建、添加、启动全部成功
}
```

概括的说,addWorker()主要做了线程池当前状态适不适合创建线程,如果适合则加入workers并启动线程执行任务。

四、excute() 第二部分

scss 复制代码
public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    /*
     * Proceed in 3 steps:
     *
     * 1. If fewer than corePoolSize threads are running, try to
     * start a new thread with the given command as its first
     * task.  The call to addWorker atomically checks runState and
     * workerCount, and so prevents false alarms that would add
     * threads when it shouldn't, by returning false.
     *
     * 2. If a task can be successfully queued, then we still need
     * to double-check whether we should have added a thread
     * (because existing ones died since last checking) or that
     * the pool shut down since entry into this method. So we
     * recheck state and if necessary roll back the enqueuing if
     * stopped, or start a new thread if there are none.
     *
     * 3. If we cannot queue task, then we try to add a new
     * thread.  If it fails, we know we are shut down or saturated
     * and so reject the task.
     */
    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)  //如果此时线程数量已经是0,创建工作线程
            addWorker(null, false);
    }
    else if (!addWorker(command, false))
        reject(command);
}

如果此时工作线程全部正在工作,又有一个新的excute()请求进入,任务将加入workQueue等待其他线程空闲后执行,并不会直接创建应急线程([coreSize,maximumPoolSize]间的线程)。

调试代码

csharp 复制代码
public class threadPool {
    public static void main(String[] args) {
/*        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);

        for(int i = 0 ;i < 5 ; i ++){
            fixedThreadPool.execute(new WorkerRunnable(i));
        }

        fixedThreadPool.shutdown();*/

        ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 60L,
                TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

        for(int i = 0;i < 5;i++){
            if(i == 2){
                executor.execute(new WorkerRunnable(i));
            }
            executor.execute(new WorkerRunnable(i));
        }
    }
}

class WorkerRunnable implements Runnable{
    private Integer num;

    WorkerRunnable(Integer num){
        this.num = num;
    }

    @Override
    public void run() {
        System.out.println("executing...");
        System.out.println("线程: " + Thread.currentThread() + " 正在执行任务");
        try {
            Thread.sleep(1000000000L);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("ending...");
    }
}

线程2并没有被创建,只是任务加入了workQueue

应急线程创建时机

scss 复制代码
if (isRunning(c) && workQueue.offer(command)) {  //将任务放入阻塞队列中
    int recheck = ctl.get();
    if (! isRunning(recheck) && remove(command)) //检查 如果不是运行态则移除刚刚放入阻塞队列的任务并调用拒绝策略
        reject(command);
    else if (workerCountOf(recheck) == 0)  //如果此时线程数量已经是0,创建工作线程
        addWorker(null, false);
}
else if (!addWorker(command, false)) //如果放入阻塞队列失败
    reject(command);

结合源码分析,如果给定的是一个有界阻塞队列,当任务不断放入阻塞队列导致阻塞队列爆满,workQueue.offer()放入任务失败,返回false,进入最后一个else if断,在else if中addWorker()的参数core=false,意味着可以创建救急线程了(迷糊了再看一下addWorker的源码分析)。

五、工作线程如何获取并执行阻塞队列中的任务

首先找到入口,该内部类实现了Runnable方法,线程start后先来这里。

csharp 复制代码
public void run() {  //入口
    runWorker(this);
}

接下来进入runWorker方法

java 复制代码
final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;  //这里获取的是第一次创建线程时分配的任务 (有点绕,看看excute()),先将它临时储存到task
    w.firstTask = null;           //设为null,为后续循环获取新的任务做准备
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        while (task != null || (task = getTask()) != null) { //如果初始任务还没执行,先进入循环块执行初始任务。如果执行过了,则循环调用getTask()方法
            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();  //真正执行任务的入口(自定义的Runnable)
                } 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;  //task设为空,保证循环获取方法
                w.completedTasks++;  //记录已经执行的数量
                w.unlock();  //解锁
            }
        }
        completedAbruptly = false; //没被打断标记
    } finally {
        processWorkerExit(w, completedAbruptly); //如果被打断了会把线程数量减到0
    }
}

这里比较简单,getTask()是线程循环获取阻塞队列方法的实现,包括KeepAlive的具体实现,getTask()才是正菜。

ini 复制代码
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())) { //如果线程池状态为SHUTDOWN且任务队列为空,或线程池将关闭且不处理新任务
            decrementWorkerCount();  //已经是关闭状态  线程数量 减 到 0
            return null;
        }

        int wc = workerCountOf(c);  //获取线程数量

        // Are workers subject to culling?
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;  //有救急线程时 timed为true

        if ((wc > maximumPoolSize || (timed && timedOut)) //执行超时(timed == true),说明有救急线程在运行
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))   //这里大概是想销毁救急线程?
                return null;
            continue;
        }

        try {
            Runnable r = timed ?//有救急线程,timed为true,则任务执行有最大时间,执行超时返回null
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :  //救急线程尝试拉取任务,如果count == 0(任务数量为0),超过等待时间后没有拿到任务则返回null,即救急线程闲了 keepAlive 秒
                workQueue.take(); //无救急线程时调用,直接返回
            if (r != null)
                return r;  //执行成功则直接return
            timedOut = true;  //超时,没拿到任务,救急线程太闲了该死了
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

六、shutdown()

scss 复制代码
public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(SHUTDOWN);
        interruptIdleWorkers();
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
}
java 复制代码
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();
    }
}

这里会打断每个线程,注意打断只是设置打断标记,runWorker()中执行完当前的作业重新循环才会感知到打断标记为true。

scss 复制代码
final void tryTerminate() {
    for (;;) {
        int c = ctl.get();
        if (isRunning(c) ||
            runStateAtLeast(c, TIDYING) ||
            (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
            return;
        if (workerCountOf(c) != 0) { // Eligible to terminate
            interruptIdleWorkers(ONLY_ONE);
            return;
        }

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                try {
                    terminated();
                } finally {
                    ctl.set(ctlOf(TERMINATED, 0));
                    termination.signalAll();
                }
                return;
            }
        } finally {
            mainLock.unlock();
        }
        // else retry on failed CAS
    }
}

尝试真正结束线程池

七、总结

执行逻辑: 程序员先创建线程池,初始状态下线程池中并没有线程。调用execute()方法后,创建Worker对象封装新的线程和初始任务(firstTask)并start线程,start后进入Worker实现的Runnable方法中,然后进入runWorker()中执行初始任务,执行完初始任务后循环调用getTask();

当调用execute()方法阻塞队列未满,最大核心线程数已满时,任务会放入阻塞队列,等待getTask()方法获取任务;

当调用execute()方法后阻塞队列已满,开始创建救急线程,救急线程同样拥有初始任务(firstTask)和新的线程,执行逻辑和第一次调用execute()相同,除了execute在KeepAlive时间内获取不到新的任务时阻塞队列会返回null,然后会关闭救急线程。

自己的debug代码

java 复制代码
package org.example.threadpool;

import java.util.concurrent.*;

/**
 * ---code_explore---
 *
 * @author summer77
 * @date 2024/1/17 14:14
 * <p>
 * ---threadPool---
 */
public class threadPool {
    public static void main(String[] args) {
/*        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);

        for(int i = 0 ;i < 5 ; i ++){
            fixedThreadPool.execute(new WorkerRunnable(i));
        }

        fixedThreadPool.shutdown();*/

        ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 3, 30L,
                TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(1),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

        for(int i = 0;i < 5;i++){
            if(i == 3){
                executor.execute(new WorkerRunnable(5,i));
            }else if(i == 2){
                executor.execute(new WorkerRunnable(40,i));
            }
            else {
                executor.execute(new WorkerRunnable(10000000,i));
            }
        }

        executor.shutdown();
    }
}

class WorkerRunnable implements Runnable{
    private Integer num;
    private Integer i;

    WorkerRunnable(Integer num,Integer i){
        this.num = num;
        this.i = i;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread() + "executing...");
        System.out.println("线程: " + Thread.currentThread() + " 正在执行任务" + i);
        try {
            Thread.sleep(num * 1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread() + "ending...");
    }
}

第一次写文,有错求指正

相关推荐
用户37215742613520 分钟前
Java 实现 Excel 与 TXT 文本高效互转
java
浮游本尊1 小时前
Java学习第22天 - 云原生与容器化
java
渣哥3 小时前
原来 Java 里线程安全集合有这么多种
java
间彧3 小时前
Spring Boot集成Spring Security完整指南
java
间彧4 小时前
Spring Secutiy基本原理及工作流程
java
Java水解5 小时前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
洛小豆7 小时前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试
前端小张同学7 小时前
服务器上如何搭建jenkins 服务CI/CD😎😎
java·后端
ytadpole7 小时前
Spring Cloud Gateway:一次不规范 URL 引发的路由转发404问题排查
java·后端
华仔啊7 小时前
基于 RuoYi-Vue 轻松实现单用户登录功能,亲测有效
java·vue.js·后端