<返回更多

一文搞懂ThreadPoolExecutor原理

2023-06-13  今日头条  IT技术控
加入收藏

 

前言

都说经典的就是好的,这句话放在JAVAThreadPoolExecutor上那是一点都没错,像现在数据库连接的池化实现,或者像Tomcat这种WEB服务器的线程管理,处处都有着ThreadPoolExecutor的影子,本篇文章将结合源码实现,对ThreadPoolExecutor的原理进行一个深入学习。

正文

一. Executor框架简介

Executor框架提供了组件来管理Java中的线程,Executor框架将其分为任务线程执行任务任务执行结果三部分。下面以表格形式对这三部分进行说明。

说明

任务

Executor框架提供了Runnable接口和Callable接口,任务需要实现这两个接口才能被线程执行

线程执行任务

Executor框架提供了接口Executor和继承于ExecutorExecutorService接口来定义任务执行机制。Executor框架中的线程池类ThreadPoolExecutor
ScheduledThreadPoolExecutor均实现了ExecutorService接口

任务执行结果

Executor框架提供了Future接口和实现了Future接口的FutureTask类来定义任务执行结果。

组件之间的类图关系如下所示。

 

Executor接口是线程池的顶层接口,通常说到的线程池指的是ThreadPoolExecutor,同时ThreadPoolExecutor还有一个子类叫做
ScheduledThreadPoolExecutor,其扩展实现了延时执行任务定时执行任务的功能。

Executor框架指的是任务执行任务的线程池任务执行结果这三部分,切不可将Executor框架与Executor接口相混淆。

本篇文章就将对Executor框架中的ThreadPoolExecutor的源码实现进行学习。

二. 认识ThreadPoolExecutor状态

在学习ThreadPoolExecutor如何执行任务前,先认识一下ThreadPoolExecutor的状态。

ThreadPoolExecutor继承于AbstractExecutorService,并实现了ExecutorService接口,是Executor框架的核心类,用于管理线程。

ThreadPoolExecutor使用了原子整型ctl来表示线程池状态和Worker数量。ctl是一个原子整型,前3位表示线程池状态,后29位表示Worker数量。ThreadPoolExecutor中这部分的源码如下所示。

java复制代码public class ThreadPoolExecutor extends AbstractExecutorService {

    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    private static final int COUNT_BITS = Integer.SIZE - 3;
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;

    // 取整型前3位,即获取线程池状态
    private static int runStateOf(int c)     { return c & ~CAPACITY; }
    // 取整型后29位,即获取Worker数量
    private static int workerCountOf(int c)  { return c & CAPACITY; }
    // 根据线程池状态和Worker数量拼装ctl
    private static int ctlOf(int rs, int wc) { return rs | wc; }

    // 线程池状态判断
    private static boolean runStateLessThan(int c, int s) {
        return c < s;
    }

    // 线程池状态判断
    private static boolean runStateAtLeast(int c, int s) {
        return c >= s;
    }

    // 判断线程池状态是否为RUNNING
    private static boolean isRunning(int c) {
        return c < SHUTDOWN;
    }
    
    ......
    
}

可知ThreadPoolExecutor有如下五种线程池状态。

得益于ctl的结构,所以无论Worker数量是多少,ThreadPoolExecutor中线程池状态存在如下关系。

RUNNING < SHUTDOWN < STOP < TIDYING < TERMINATED

因此runStateLessThan()runStateAtLeast() 和isRunning() 方法可以方便的对线程池状态进行判断。

三. 执行任务源码分析

作为线程池,ThreadPoolExecutor最重要也最经典的地方,当然就是执行任务了。本节对ThreadPoolExecutor执行任务的流程进行一个学习。

ThreadPoolExecutor中执行任务的入口方法为execute(),其实现如下。

java复制代码public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    // 如果Worker数量小于核心线程数,则创建Worker并执行任务
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    // 如果Worker数量大于等于核心线程数,则将任务添加到任务阻塞队列
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        // 如果线程池状态突然不再是RUNNING,则尝试将任务从任务阻塞队列中删除,删除成功则为该任务执行拒绝策略
        if (! isRunning(recheck) && remove(command))
            reject(command);
        // 如果线程池中Worker数量突然为0,则创建一个Worker来执行任务
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    // 执行到这里表示线程池状态已经不是RUNNING或者任务阻塞队列已满
    // 此时尝试新建一个Worker来执行任务
    // 如果新建一个Worker来执行任务失败,表明线程池状态不再是RUNNING或者Worker数量已经达到最大线程数,此时执行拒绝策略
    else if (!addWorker(command, false))
        reject(command);
}

execute() 中会根据Worker数量和线程池状态来决定是新建Worker来执行任务还是将任务添加到任务阻塞队列。新建Worker来执行任务的实现如下所示。

java复制代码private boolean addWorker(Runnable firstTask, boolean core) {
    // 标记外层for循环
    retry:
    for (;;) {
        int c = ctl.get();
        // 获取线程池状态
        int rs = runStateOf(c);

        // 线程池状态为RUNNING时,可以创建Worker
        // 线程池状态为SHUTDOWN,且任务阻塞队列不为空时,可以创建初始任务为null的Worker
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;

        for (;;) {
            // 获取Worker数量
            int wc = workerCountOf(c);
            
            // 如果Worker数量大于CAPACITY,拒绝创建Worker
            // core为true表示创建核心线程Worker,如果Worker数量此时已经大于等于核心线程数,则拒绝创建Worker,转而应该将任务添加到任务阻塞队列
            // core为false表示创建非核心线程Worker,如果Worker数量此时已经大于等于最大线程数,则拒绝创建Worker,转而应该执行拒绝策略
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            // 以CAS方式将Worker数量加1
            // 加1成功表明无竞争发生,从外层for循环跳出
            if (compareAndIncrementWorkerCount(c))
                break retry;
            // 加1失败表明有竞争发生,此时需要重新获取ctl的值
            c = ctl.get();
            // 重新获取ctl后如果发现线程池状态发生了改变,此时重新执行外层for循环,即需要基于新的线程池状态判断是否允许创建Worker
            // 重新获取ctl后如果线程池状态未发生改变,则继续执行内层for循环,即尝试再一次以CAS方式将Worker数量加1
            if (runStateOf(c) != rs)
                continue retry;
        }
    }

    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        // 创建一个Worker
        w = new Worker(firstTask);
        // 获取Worker的线程
        final Thread t = w.thread;
        if (t != null) {
            final ReentrantLock mAInLock = this.mainLock;
            // 由于线程池中存储Worker的集合为HashSet,因此将Worker添加到Worker集合时需要获取全局锁保证线程安全
            mainLock.lock();
            try {
                // 再一次获取线程池状态
                int rs = runStateOf(ctl.get());

                // 如果线程池状态还是为RUNNING或者线程池状态为SHUTDOWN但创建的Worker的初始任务为null,则允许将创建出来的Worker添加到集合
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    // 检查一下Worker的线程是否可以启动(处于活动状态的线程无法再启动)
                    if (t.isAlive())
                        throw new IllegalThreadStateException();
                    // 将Worker添加到Worker集合
                    workers.add(w);
                    int s = workers.size();
                    // largestPoolSize用于记录线程池最多存在过的Worker数
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                // 启动Worker线程
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            // Worker线程没有成功启动起来,此时需要对该Worker的创建执行回滚操作
            addWorkerFailed(w);
    }
    return workerStarted;
}

addWorker() 方法中只允许两种情况可以创建Worker

一旦Worker创建成功,就会将Worker的线程启动,如果Worker创建失败或者Worker的线程启动失败,则会调用addWorkerFailed() 方法执行回滚操作,其实现如下所示。

java复制代码private void addWorkerFailed(Worker w) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 如果Worker添加到了Worker集合中,则将Worker从Worker集合中删除
        if (w != null)
            workers.remove(w);
        // 以CAS方式将Worker数量减1
        decrementWorkerCount();
        // 尝试终止线程池
        tryTerminate();
    } finally {
        mainLock.unlock();
    }
}

由于Worker自身实现了Runnable,因此Worker自身就是一个任务,实际上Worker的线程执行的任务就是Worker本身,因此addWorker() 中将Worker的线程启动时,会调用Workerrun() 方法,其实现如下。

java复制代码public void run() {
    runWorker(this);
}

Workerrun() 方法中调用了ThreadPoolExecutorrunWorker() 方法,其实现如下所示。

java复制代码final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock();
    boolean completedAbruptly = true;
    try {
        // 如果task为null,则从任务阻塞队列中获取任务
        // 通常Worker启动时会先执行初始任务,然后再去任务阻塞队列中获取任务
        while (task != null || (task = getTask()) != null) {
            w.lock();
            // 线程池正在停止时,需要确保当前Worker的线程是被中断的
            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执行任务发生异常或者从getTask()中获取任务为空时会执行这里的逻辑
        // processWorkerExit()会将Worker从Worker集合中删除,并尝试终止线程池
        processWorkerExit(w, completedAbruptly);
    }
}

runWorker() 方法就是先让Worker将初始任务(如果有的话)执行完,然后循环从任务阻塞队列中获取任务来执行,如果Worker执行任务发生异常或者从任务阻塞队列获取任务失败(获取到的任务为null),则调用processWorkerExit() 方法来将自身从Worker集合中删除。下面先看一下getTask() 方法的实现。

java复制代码private Runnable getTask() {
    boolean timedOut = false;

    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // 如果线程池状态为SHUTDOWN,且任务阻塞队列为空,则不再允许从任务阻塞队列中获取任务
        // 如果线程池状态为STOP,则不再允许从任务阻塞队列中获取任务
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

        // 如果allowCoreThreadTimeOut为true,或者当前线程数大于核心线程数,此时timed为true,表明从任务阻塞队列以超时退出的方式获取任务
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        // 如果当前线程数大于最大线程数,则当前Worker应该被删除
        // 如果当前Worker上一次从任务阻塞队列中获取任务时超时退出,且任务阻塞队列现在还是为空,则当前Worker应该被删除
        if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
            // 从任务阻塞队列中获取任务
            Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
            if (r != null)
                // 获取到任务则返回该任务
                return r;
            // timedOut为true表明Worker上一次从任务阻塞队列中获取任务时超时退出
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

getTask() 方法在如下情况不允许Worker从任务阻塞队列中获取任务。

如果Worker有资格从任务阻塞队列获取任务,那么当allowCoreThreadTimeOuttrue,或者当前线程数大于核心线程数时,Worker以超时退出的方式获取任务,否则Worker以一直阻塞的方式获取任务。

WorkergetTask() 方法中获取任务失败时,getTask() 方法会返回null,从而导致Worker会执行processWorkerExit() 方法来删除自身,其实现如下所示。

java复制代码private void processWorkerExit(Worker w, boolean completedAbruptly) {
    // completedAbruptly为true表明是执行任务时发生异常导致Worker需要被删除
    if (completedAbruptly)
        // 修正Worker数量
        decrementWorkerCount();

    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        completedTaskCount += w.completedTasks;
        // 将Worker从Worker集合中删除
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }

    // 尝试终止线程池
    tryTerminate();

    int c = ctl.get();
    if (runStateLessThan(c, STOP)) {
        if (!completedAbruptly) {
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
            if (workerCountOf(c) >= min)
                return;
        }
        addWorker(null, false);
    }
}

WorkerprocessWorkerExit() 方法中删除自身之后,还会调用tryTerminate() 尝试终止线程池,tryTerminate() 方法很精髓,后面会对其进行详细分析,这里暂且不谈。至此,Worker的创建,执行任务,获取任务和删除的整个流程已经大体分析完毕。

对于执行任务,现在简单进行一个小结

ThreadPoolExecutor执行任务,第一步是根据Worker数量来决定是新建Worker来执行任务还是将任务添加到任务阻塞队列,这里的判断规则如下。

  1. 如果Worker数量小于核心线程数,则创建Worker来执行任务;
  2. 如果Worker数量大于等于核心线程数,则将任务添加到任务阻塞队列;
  3. 如果任务阻塞队列已满,则创建Worker来执行任务;
  4. 如果Worker数量已经达到最大线程数,此时执行任务拒绝策略。

当要新建Worker来执行任务时,只有两种情况可以新建Worker,如下所示。

  1. 线程池状态为RUNNING,可以创建Worker
  2. 线程池状态为SHUTDOWN,且任务阻塞队列不为空,可以创建初始任务为nullWorker

Worker自身实现了Runnable,且Worker持有一个线程,当Worker启动时,就是启动Worker持有的线程,而这个线程执行的任务就是Worker自身。

Worker启动后,会首先执行自己的初始任务,然后再去任务阻塞队列中获取任务。

四. 关闭线程池源码分析

不再使用的线程池,可以进行关闭。关闭ThreadPoolExecutor的方法有shutdown() 和shutdownNow(),本节将对ThreadPoolExecutor的关闭进行分析。

1. shutdown()

首先分析shutdown() 方法,其实现如下。

java复制代码public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        // 循环通过CAS方式将线程池状态置为SHUTDOWN
        advanceRunState(SHUTDOWN);
        // 中断空闲Worker
        interruptIdleWorkers();
        onShutdown();
    } finally {
        mainLock.unlock();
    }
    // 尝试终止线程池
    tryTerminate();
}

shutdown() 方法中首先会将线程池状态置为SHUTDOWN,然后调用interruptIdleWorkers() 方法中断空闲Worker,最后调用tryTerminate() 方法来尝试终止线程池。那么这里要解释一下什么是空闲Worker,先看一下interruptIdleWorkers() 的实现。

java复制代码private void interruptIdleWorkers() {
    interruptIdleWorkers(false);
}

private void interruptIdleWorkers(boolean onlyOne) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers) {
            Thread t = w.thread;
            // 中断线程前需要先尝试获取Worker的锁
            // 只能获取到空闲Worker的锁,所以shutdown()方法只会中断空闲Worker
            if (!t.isInterrupted() && w.tryLock()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                } finally {
                    w.unlock();
                }
            }
            if (onlyOne)
                break;
        }
    } finally {
        mainLock.unlock();
    }
}

调用interruptIdleWorkers() 方法中断Worker前首先需要尝试获取Worker的锁,已知Worker除了实现Runnable接口外,还继承于
AbstractQueuedSynchronizer,因此Worker本身是一把锁,然后在runWorker() 中Worker执行任务前都会先获取Worker的锁,这里看一下Workerlock() 方法的实现。

java复制代码public void lock() {
    acquire(1);
}

protected boolean tryAcquire(int unused) {
    // 以CAS方式将state从0设置为1
    if (compareAndSetState(0, 1)) {
        setExclusiveOwnerThread(Thread.currentThread());
        return true;
    }
    return false;
}

可以发现,Workerlock() 中调用了acquire() 方法,该方法由
AbstractQueuedSynchronizer抽象类提供,在acquire() 中会调用其子类实现的tryAcquire() 方法,tryAcquire() 方法会以CAS方式将state从0设置为1,因此这样的设计Worker是一把不可重入锁。

回到interruptIdleWorkers() 方法,前面提到该方法中断Worker前会尝试获取Worker的锁,能够获取到锁才会中断Worker,而因为Worker是不可重入锁,所以正在执行任务的Worker是无法获取到锁的,只有那些没有执行任务的Worker的锁才能够被获取,因此所谓的中断空闲Worker,实际就是中断没有执行任务的Worker,那些执行任务的Workershutdown() 方法被调用时不会被中断,这些Worker执行完任务后会继续从任务阻塞队列中获取任务来执行,直到任务阻塞队列为空,此时没有被中断过的Worker也会被删除掉,等到线程池中没有Worker以及任务阻塞队列没有任务后,线程池才会被终止掉。

对于shutdown() 方法,一句话总结就是:将线程池状态置为SHUTDOWN并拒绝接受新任务,等到线程池Worker数量为0,任务阻塞队列为空时,关闭线程池。

2. shutdownNow()

现在再来分析shutdownNow() 方法。

java复制代码public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        // 循环通过CAS方式将线程池状态置为STOP
        advanceRunState(STOP);
        // 中断所有Worker
        interruptWorkers();
        // 将任务阻塞队列中的任务获取出来并返回
        tasks = drainQueue();
    } finally {
        mainLock.unlock();
    }
    // 尝试终止线程池
    tryTerminate();
    return tasks;
}

private void interruptWorkers() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 中断线程池中所有Worker
        for (Worker w : workers)
            w.interruptIfStarted();
    } finally {
        mainLock.unlock();
    }
}

shutdownNow() 方法中首先会将线程池状态置为STOP,然后调用interruptWorkers() 方法中断线程池中的所有Worker,接着调用tryTerminate() 方法来尝试终止线程池,最后shutdownNow() 方法会将任务阻塞队列中还未被执行的任务返回。

shutdownNow() 方法调用之后,线程池中的所有Worker都会被中断,包括正在执行任务的Worker,等到所有Worker都被删除之后,线程池即被终止,也就是说,shutdownNow() 不会保证当前时刻正在执行的任务会被安全的执行完,并且会放弃执行任务阻塞队列中的所有任务。

3. tryTerminate()

关于线程池的关闭,还有一个重要的方法,那就是前面多次提到的tryTerminate() 方法,该方法能确保线程池可以被正确的关闭,其实现如下所示。

java复制代码final void tryTerminate() {
    for (;;) {
        int c = ctl.get();
        // 如果线程池状态为RUNNING,则没有资格终止线程池
        // 如果线程池状态大于等于TIDYING,则没有资格终止线程池
        // 如果线程池状态为SHUTDOWN但任务阻塞队列不为空,则没有资格终止线程池
        if (isRunning(c) ||
                runStateAtLeast(c, TIDYING) ||
                (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
            return;
        // 线程池状态为SHUTDOWN且任务阻塞队列为空会执行到这里
        // 线程池状态为STOP会执行到这里
        // Worker数量不为0,表明当前还有正在执行任务的Worker或者空闲的Worker,此时中断一个空闲的Worker
        // 在这里被中断的空闲Worker会在getTask()方法中返回null,从而执行processWorkerExit(),最终该Worker会被删除
        // processWorkerExit()方法中又会调用tryTerminate(),因此将shutdown信号在空闲Worker之间进行了传播
        if (workerCountOf(c) != 0) {
            interruptIdleWorkers(ONLY_ONE);
            return;
        }

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            // 将线程池状态置为TIDYING
            if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                try {
                    // 终止线程池
                    terminated();
                } finally {
                    // 将线程池状态最终置为TERMINATED
                    ctl.set(ctlOf(TERMINATED, 0));
                    termination.signalAll();
                }
                return;
            }
        } finally {
            mainLock.unlock();
        }
    }
}

tryTerminate() 方法的官方注释中给出了两种线程池会被终止的情况:

官方注释中还说明在所有可能导致线程池终止的操作中都应该调用tryTerminate() 方法来尝试终止线程池,因此线程池中Worker被删除时任务阻塞队列中任务被删除时会调用tryTerminate(),以达到在线程池符合终止条件时及时终止线程池。

4. 小结

对于关闭线程池,简单小结如下。

关闭ThreadPoolExecutor有两种方式,如下所示。

  1. shutdown()。调用shutdown() 方法会首先将线程池状态置为SHUTDOWN并拒绝接受新任务,然后中断空闲Worker,等到线程池中Worker数量为0,任务阻塞队列为空时,线程池被真正关闭;
  2. shutdownNow()。调用shutdownNow() 方法会首先将线程池状态置为STOP,然后中断所有Worker(包括正在执行任务的Worker),并将任务阻塞队列中还未被执行的任务返回,当线程池Worker数量为0时,线程池被真正关闭。

还有一点需要说明,Worker除了实现Runnable接口外,还继承于
AbstractQueuedSynchronizer,因此Worker本身是一把锁,Worker执行任务前都会先获取Worker的锁,所以正在执行任务的Worker的锁是无法被获取的,换言之,只有没有执行任务的Worker的锁才能被获取,这些Worker就称为空闲Worker

声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>