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...");
    }
}

第一次写文,有错求指正

相关推荐
BUG批量生产者6 分钟前
[746] 使用最小花费爬楼梯
java·开发语言
慕y27410 分钟前
Java学习第二十四部分——JavaServer Faces (JSF)
java·开发语言·学习
JosieBook28 分钟前
【Java编程动手学】深入剖析Java网络编程:原理、协议与应用
java·udp·tcp
black_blank29 分钟前
st表 && csp37 第四题 集体锻炼
java·数据结构·算法
我爱Jack32 分钟前
Java List 使用详解:从入门到精通
java·开发语言·数据结构
手握风云-39 分钟前
JavaEE初阶第八期:解锁多线程,从 “单车道” 到 “高速公路” 的编程升级(六)
java·开发语言
天南星1 小时前
java-WebSocket在Java生态中的发展历程
java·后端·websocket
chuanauc1 小时前
记录一次在 centos 虚拟机 中 安装 Java环境
java·linux·centos
写不出来就跑路1 小时前
SpringBoot静态资源与缓存配置全解析
java·开发语言·spring boot·spring·springboot
墨着染霜华2 小时前
Caffeine的tokenCache与Spring的CaffeineCacheManager缓存区别
java·spring·缓存