线程池源码分析

69 阅读4分钟

线程池的作用

线程池的作用主要有以下几种,分别是减少线程的开销、异步执行代码、简化线程管理等。线程池减少线程的开销主要的原因是 JVM 的线程与内核线程是 1:1 的,也就是说创建一个线程需要创建一个内核线程。什么是内核线程,内核线程代表的是由操作系统内核直接调度和管理的线程,由操作系统进行任务调度的基本单位。内核线程的创建、调度和销毁均由操作系统负责。内核维护其上下文,并决定何时在 CPU 去执行。所以创建或者销毁一个线程是比较大的开销。使用线程池可以重复利用线程,减少开销。

线程池源码分析

1、核心变量与状态控制

ThreadPoolExecutor 用 ctl 变(AtomicInteger)同时存储线程池状态和线程数量。通过位运算高效管理。

// 高3位存储线程池状态(RUNNING, SHUTDOWN, etc.),低29位存储工作线程数量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

// 状态常量(二进制高3位)
private static final int RUNNING    = -1 << COUNT_BITS; // 111 00000...
private static final int SHUTDOWN   =  0 << COUNT_BITS; // 000 00000...
private static final int STOP       =  1 << COUNT_BITS; // 001 00000...
private static final int TIDYING    =  2 << COUNT_BITS; // 010 00000...
private static final int TERMINATED =  3 << COUNT_BITS; // 011 00000...

// 解析线程池状态和线程数的方法
private static int runStateOf(int c)     { return c & ~CAPACITY; }
private static int workerCountOf(int c)  { return c & CAPACITY; }

状态流转为:RUNNING -> SHUTDOWN -> STOP -> TIDYING -> TERMINATED

  • RUNNING:接受新任务,也能处理阻塞队列中的任务
  • SHUTDOWN:不接受新任务,但是处理阻塞队列中的任务
  • STOP:不接受新任务,也不处理阻塞队列中的任务,中断处理过程中的任务
  • TIDYING:当所有的任务都执行完了,当前线程池中已经没有工作线程,这时线程池会转换为 TIDYING 状态,并且要调用 terminated 方法
  • TERMINATED:terminated 方法调用完成

2、Worker 线程管理

Worker 类封装了工作线程,继承了 AQS(AbstractQueuedSynchronizer),实现不可重入锁,用于防止任务执行期间被中断

private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
    final Thread thread; // 实际执行任务的线程(由ThreadFactory创建)
    Runnable firstTask;  // 初始任务(可能为null)

    Worker(Runnable firstTask) {
        setState(-1); // 初始状态禁止中断,直到调用runWorker()
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this); // 通过工厂创建线程
    }

    public void run() {
        runWorker(this); // 核心执行逻辑(见下文)
    }

    // 通过AQS实现锁机制
    protected boolean isHeldExclusively() { return getState() != 0; }
    protected boolean tryAcquire(int unused) { /* ... */ }
    protected boolean tryRelease(int unused) { /* ... */ }
}

3、核心方法 runWorker

每个 Worker 线程在 runWorker() 中循环获取并执行任务。

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // 允许中断
    boolean completedAbruptly = true;
    try {
        // 循环获取任务:先从firstTask取,再从队列中poll/take
        while (task != null || (task = getTask()) != null) {
            w.lock(); // 加锁防止任务执行时被中断
            if ((runStateAtLeast(ctl.get(), STOP) || 
                 (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
                beforeExecute(wt, task); // 钩子方法(可扩展)
                try {
                    task.run(); // 执行任务(注意:此处是直接调用,非新线程)
                    afterExecute(task, null); // 钩子方法
                } catch (Throwable ex) {
                    afterExecute(task, ex); // 异常处理钩子
                    throw ex;
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly); // Worker退出清理逻辑
    }
}

4、任务获取 getTask()

线程从阻塞队列中获取任务,根据配置决定是否超时等待。

private Runnable getTask() {
    boolean timedOut = false;
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // 检查线程池是否已关闭且队列为空,若满足则返回null,Worker退出
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        // 线程数超过maximumPoolSize或需要超时回收,且超时未获取任务,减少Worker数量
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
            // 根据timed决定是poll(超时等待)还是take(阻塞等待)
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            timedOut = true; // 超时标记
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

5、任务提交 execute()

任务提交的核心逻辑,决定是直接执行、入队还是拒绝。

public void execute(Runnable command) {
    if (command == null) throw new NullPointerException();
    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) { // 当前线程数 < corePoolSize
        if (addWorker(command, true))      // 尝试创建新Worker(核心线程)
            return;
        c = ctl.get(); // 如果addWorker失败(如线程池已关闭),重新获取状态
    }
    if (isRunning(c) && workQueue.offer(command)) { // 尝试入队
        int recheck = ctl.get();
        if (!isRunning(recheck) && remove(command)) // 二次检查线程池状态
            reject(command); // 若线程池已关闭,移除任务并拒绝
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false); // 保证至少有一个线程处理队列任务
    } else if (!addWorker(command, false)) // 尝试创建非核心线程
        reject(command); // 队列满且线程数已达maximumPoolSize,触发拒绝策略
}