ThreadPoolExecutor 源码解析(含流程图)

2,021 阅读7分钟

前言

线程池大家都用过,但是线程池的内部源码,大家可能不太了解。没了解过源码的小伙伴可以通过该文章了解,了解过的也可以先思考下了解到什么程度,然后再和本文进行对比,可能会有新的理解。 本文大纲

手写简易线程池

为了更好地理解线程池,手写了一个简单的线程池。同样使用阻塞队列,线程数量是固定的,线程池创建时就创建和运行所有线程。虽然和 ThreadPoolExecutor 相比差很低,但是也达到复用线程的目的,核心思想不变,都是生产者消费者模型

public class MyThreadPool implements Executor {

    /**
     * Worker 列表
     */
    private List<Worker> workers;

    /**
     * 任务阻塞队列
     */
    private final BlockingQueue<Runnable> tasks = new LinkedBlockingQueue<>();

    private final static int NCPU = Runtime.getRuntime().availableProcessors();

    /**
     * 核心线程数量
     */
    private final int coreSize;

    /**
     * 线程工厂
     */
    private final ThreadFactory threadFactory;

    /**
     * 终止标志
     */
    private volatile boolean terminated;

    private static final ThreadFactory DEFAULT_THREAD_FACTORY = new ThreadFactory() {

        private final AtomicInteger counter = new AtomicInteger();

        @Override
        public Thread newThread(Runnable r) {
            return new Thread(r, "default-pool-" + counter.getAndIncrement());
        }
    };

    public MyThreadPool() {
        this(NCPU, DEFAULT_THREAD_FACTORY);
    }

    public MyThreadPool(int coreSize, ThreadFactory threadFactory) {
        checkParameter(coreSize, threadFactory);
        this.coreSize = coreSize;
        this.threadFactory = threadFactory;
        init();
    }

    private void checkParameter(int coreSize, ThreadFactory threadFactory) {
        if (coreSize <= 0) {
            throw new IllegalArgumentException("coreSize must more than zero");
        }
        if (threadFactory == null) {
            throw new NullPointerException("threadFactory cant be none");
        }
    }

    private void init() {
        workers = new ArrayList<>(coreSize);
        for (int i = 0; i < coreSize; i++) {
            workers.add(new Worker());
        }
    }

    public void stop() {
        terminated = true;
        invokerAllThreadInterrupt();
    }

    private void invokerAllThreadInterrupt() {
        for (Worker worker : workers) {
            worker.thread.interrupt();
        }
    }

    @Override
    public void execute(Runnable command) {
        try {
            tasks.put(command);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private class Worker implements Runnable {

        private final Thread thread;

        private Worker() {
            this.thread = threadFactory.newThread(this);
            thread.start();
        }

        @Override
        public void run() {
            Thread thread = Thread.currentThread();
            while (!terminated && !thread.isInterrupted()) {
                try {
                    // 获取任务
                    Runnable t = tasks.take();
                    t.run();
                } catch (InterruptedException e) {
                    thread.interrupt();
                    break;
                }
            }
        }
    }
}

运行测试

public static void main(String[] args) throws InterruptedException {
    MyThreadPool pool = new MyThreadPool();
    pool.execute(() -> {
        Thread thread = Thread.currentThread();
        while (!thread.isInterrupted()) {
            try {
                Thread.sleep(1000L);
                System.out.println(Thread.currentThread().getName() + 
                                " hello " + System.currentTimeMillis());
            } catch (InterruptedException e) {
                thread.interrupt();
            }
        }
    });
}

UML 类图

UML 类图

设计理念

线程池本质上对应生产者消费者模型

  • 生产者:调用 execute() 方法提交任务的线程
  • 消费者:线程池中的 Worker,不断循环获取阻塞队列中的任务
  • 中间层:阻塞队列,用于存放任务,将生产者和消费者解耦,生产者(线程)只管生产,消费者(Worker 线程)只管消费

构造方法参数

  • int corePoolSize:核心线程池大小
  • int maximumPoolSize:最大线程池大小,必须大于等于核心线程池
  • long keepAliveTime:非核心线程池的最大存活时间
  • TimeUnit unit:存活时间的时间单位
  • BlockingQueue<Runnable> workQueue:任务阻塞队列
  • ThreadFactory threadFactory:线程工厂
  • RejectedExecutionHandler handler:拒绝策略处理器
    • AbortPolicy : 直接抛出异常
    • CallerRunsPolicy : 让调用 execute 方法的线程执行任务
    • DiscardOldestPolicy : 丢弃最先入队的任务
    • DiscardPolicy : 丢弃当前任务

线程池状态

  • RUNNING:接受新任务并处理任务队列的任务
  • SHUTDOWN:不接受新任务,但处理任务队列的任务
  • STOP:不接受新任务,不处理任务队列的任务,并中断正在进行的任务
  • TIDYING:所有任务都已终止,workerCount 为 0,线程转换为 TIDYING 状态,将运行 terminated() 钩子方法
  • TERMINATEDterminate() 方法执行完成

状态转换

RUNNING -> SHUTDOWN
    调用 shutdown() 方法,finalize() 方法被调用时也会调用 shutdown() 方法
(RUNNING or SHUTDOWN) -> STOP
	调用 shutdownNow() 方法
SHUTDOWN -> TIDYING
	任务队列和线程列表都为空
STOP -> TIDYING
	线程列表都为空
TIDYING -> TERMINATED
	terminated() 执行完成

状态转换图 状态转换图

内部类 - Worker

Worker 内部封装了一个线程,用来执行任务,而且 Worker 类实现了 AbstractQueuedSynchronizer,用来实现对临界资源的加解锁。

private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable{
    /** 用以运行任务的线程 */
    final Thread thread;
    /** Worker 的第一个任务,可以为空,为空不执行 */
    Runnable firstTask;
    /** 任务完成数 */
    volatile long completedTasks;
    
    Worker(Runnable firstTask) {
        // 此处进行加锁,防止未初始化完成就被运行
        // 在 runWorker() 方法中会进行解锁
        setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);
    }
}

核心方法

execute

java.util.concurrent.ThreadPoolExecutor#execute

该方法用来执行任务,主要有三个分支,分支 1,线程数小于核心线程数量,创建线程分支 2,线程数大于等于核心线程数量,将任务放入任务队列分支 3,任务队列已满,创建非核心线程,创建失败表明线程数大于大于等于最大线程数量,执行拒绝策略

流程伪代码

if 线程数 < 核心线程池(此时线程数小于 corePoolSize)
    if 添加核心线程成功
        返回
if 线程池运行中 && 提交任务到任务队列成功(此时线程数大于等于 corePoolSize)
    if 线程池非运行 && 移除任务成功
        执行拒绝策略(由于线程池正在停止)
    else if 线程数为 0
        添加非核心线程池
 if 新增非核心线程池失败
     执行拒绝策略(由于线程数大于等于 maximumPoolSize)

具体源码讲解

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();

    int c = ctl.get();
    // 分支 1: 当前线程数小于 corePoolSize
    if (workerCountOf(c) < corePoolSize) {
        // 新增核心线程
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    // 分支 2: 将任务提交到任务队列(此时线程数大于等于 corePoolSize)
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        // 重新校验线程池(如果线程池正在停止,那么移除任务)
        // 移除成功,执行拒绝策略
        if (!isRunning(recheck) && remove(command))
            // 执行拒绝策略(由于线程池正在停止)
            reject(command);
        // 线程数为 0(此时任务队列中有任务,需要创建一个非核心线程池)
        // 例如我们将 corePoolSize 设置成 0,就会进入此逻辑
        else if (workerCountOf(recheck) == 0)
            // 新增非核心线程
            addWorker(null, false);
    }
    // 分支 3: 新增非核心线程(此时任务队列已满)
    // 新增失败,执行拒绝策略
    else if (!addWorker(command, false))
        // 执行拒绝策略(由于线程数大于等于 maximumPoolSize)
        reject(command);
}

// submit 调用的还是 execute
public <T> Future<T> submit(Callable<T> task) {
    if (task == null) throw new NullPointerException();
    // 将 task 包装成 FutureTask
    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);
    return ftask;
}

protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
    return new FutureTask<T>(callable);
}

addWorker

java.util.concurrent.ThreadPoolExecutor#addWorker

该方法用来创建 Worker,并且放入 workers 中。主要分两块逻辑,分支 1 先将线程数的计数 + 1,分支 2 再创建 Worker 对象,再运行 Worker 对象内部的 Thread

流程伪代码

分支 1:将线程池的 Worker 计数值 + 1
for 循环(负责判断线程是否停止,并且提前返回)
	if state >=SHUTDOWN && !(SHUTDOWN && 首个任务为 null && 工作队列非空)
		return false
	for 循环(负责将线程池的 Worker 计数值 + 1)
		if 线程数 >= 容量 
            || 当前线程数大于等于core(创建核心线程)
            || 当前线程数大于等于maxiumPoolSize(创建非核心线程)
			return false
	if cas 增加 Worker 数量成功
		break 外层循环,跳到下层逻辑
	if 运行状态发生变化(线程可能正被终止)
		到外层重新循环
    继续进行内层循环
        
分支 2:创建 Worker 并执行 Worker 内部的线程(此时线程数量自增成功)
提前创建Worker对象,创建时将state设置为-1(防止创建未完成就进行运行)
if 线程非空(防止Worker未初始化成功)才运行下面的逻辑
加锁
    if state < SHUTDOWN || (SHUTDOWN && 任务为空)
        if 线程已运行
            抛出异常
        添加Worker到workers
        更新largestPoolSize
        设置状态为true(表明添加成功)
解锁
if 添加成功
	执行Worker的线程
finally
    if Worker线程未被执行
		进行添加失败处理

具体源码讲解

private boolean addWorker(Runnable firstTask, boolean core) {
    // 分支 1:将线程池的 Worker 计数值 + 1
    retry:
    // 外层循环,主要用来检查线程是否正在终止
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // Check if queue empty only if necessary.
        // 如果是STOP、TIDING和TERMINATED 状态,直接返回fasle,表明添加失败
        // 如果是SHUTDOWN状态,或者workQueue为空,或者fisrtTask不为空,也会直接返回fasle
        // 即想要创建Worker,要么得是RUNNABLE状态,要么是SHUTDOWN状态,且firstTask为空,workQueue不空
        // 因为SHUTDOWN状态下,不会接收新的任务,但是会处理队列中已有的任务
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;

        // 内存循环将线程池的 Worker 计数值 + 1
        for (;;) {
            int wc = workerCountOf(c);
            // 如果当前线程数大于等于最大容量
            // 或者创建核心线程池时,当前线程数大于等于核心线程数
            // 或者创建非核心线程池,当前线程数大于等于最大线程数
            // 都会返回false
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            // 根据CAS自增 c
            if (compareAndIncrementWorkerCount(c))
                // 自增成功,跳出外层循环,进入分支2
                break retry;
            c = ctl.get();  // Re-read ctl
            // 状态发送改变,变成非运行状态,进行外层循环判断
            if (runStateOf(c) != rs)
                continue retry;
            // 如果状态未变,那么就只是CAS失败,只需要进入内部循环重新自旋CAS
            // else CAS failed due to workerCount change; retry inner loop
        }
    }

    // 分支 2:创建 Worker,并且加入到 workers 中,且运行 Worker 内部的线程
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        // 提前创建Worker,空间换时间
        // 构造方法中会进行加锁,防止Worker未初始化完成就被使用
        // runWorker方法会进行解锁
        w = new Worker(firstTask);
        final Thread t = w.thread;
        // 此处的判断就是为了防止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());
                // 如果处于运行状态,或者处于SHUTDOWN,但是有任务需要执行,下面都会开启线程
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    // 如果该线程已经在运行了
                    if (t.isAlive()) // precheck that t is startable
                        // 直接抛出异常
                        throw new IllegalThreadStateException();
                    workers.add(w);
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            // 执行添加失败的逻辑
            // CAS自减线程数,移除Worker
            addWorkerFailed(w);
    }
    return workerStarted;
}

通过源码,可以总结出创建 Worker 失败的原因:

  1. 在添加时线程池被停止了
  2. 添加核心线程池时,超过核心线程池数量
  3. 添加非核心线程池时,超过最大线程池数量
  4. Worker 对象初始化未完成时就被使用,即 thread 对象还未完全创建
  5. 当前线程正在被运行(防止出现重复运行,抛出异常)
  6. 线程创建过多,导致 OOM

runWorker

java.util.concurrent.ThreadPoolExecutor#runWorker

该方法用来让 Worker 不断循环获取任务,不断执行,直到线程被终止才会停止循环,并且执行退出后的逻辑。

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 {
        // 此处表明线程池被终止了,Worker 停止了循环
        // 将执行 Worker 退出后的逻辑,将 Worker 从 workers 移除,减线程数计数
        // 自如果任务队列为空且线程数为 0,就尝试转换状态为 TIDING 或者 TERMINATED
        processWorkerExit(w, completedAbruptly);
    }
}

流程图

线程池核心流程图

小结

  • 线程池的本质就是一种池化的思想,复用线程,减少线程的创建和销毁的开销。常量池和连接池也都是这样的思想。
  • 线程池的设计思路是生产者和消费者模型,通过队列进行解耦,而使用阻塞队列则是为了并发安全和不必要的自旋。其实消息队列也是生产者和消费者模型,因为消息队列和阻塞队列都是队列。
  • 线程池如果用不好可能会死锁,即尽量不要在线程池内部使用 execute 方法提交任务,因为可能会出现循环等待的情况。而且线程池使用 submit() 方法要注意选择合适的拒绝策略,因为 submit() 会将任务包装成 FutureTask 对象,如果任务被拒绝,即没有调用 run() 方法,那么调用 get() 方法的线程会被阻塞住。