线程池ThreadPoolExecutor原理分析

通常我们创建线程池有两种方式,一是通过ThreadPoolExecutor构造函数实现(推荐);二是通过Executor框架的工具类Executors来自行创建。本文主要分析ThreadPoolExecutor的执行原理,包括线程的创建、执行、销毁。在进行详细分析之前,先介绍一下ThreadPoolExecutor类中的一些关键变量。

  • ctl:它是AtomicInteger类型的变量,同时存储了线程池当前线程数量以及线程池状态两个信息,runStateOf(ctl)返回的就是当前线程池的状态,workerCountOf(ctl)返回的是线程数量;
  • workQueue: 当核心线程数满了后,用于存储待执行任务的队列 (private final BlockingQueue<Runnable> workQueue);
  • mainLock: 一个ReentrantLock类型的锁,是线程池的内置锁,那么我们可以推测出可能线程池的许多方法都可能会使用到这把锁;
  • workers: 它是存储worker(工作线程)的HashSet类型集合,从它的构造函数可以看出firstTask是Worker启动后第一个执行的任务,是在Worker创建时就被添加的,thread是运行Worker的启动线程;
Worker(Runnable firstTask) {
      setState(-1); // inhibit interrupts until runWorker
      this.firstTask = firstTask;
      this.thread = getThreadFactory().newThread(this);
}

线程池的5种状态:

// 线程池一旦被创建,就处于RUNNING状态,表示能够接收新任务以及对已添加的任务进行处理
private static final int RUNNING    = -1 << COUNT_BITS;
// 表示不接收新任务,但能处理已添加的任务
private static final int SHUTDOWN   =  0 << COUNT_BITS;
// 表示不接收新任务,不处理已添加的任务,并且会中断正在处理的任务
private static final int STOP       =  1 << COUNT_BITS;
// 当所有的任务已终止,ctl记录的任务数为0,线程池的状态会变为TIDYING状态
private static final int TIDYING    =  2 << COUNT_BITS;
// 线程池彻底终止,就会变成TERMINATED状态
private static final int TERMINATED =  3 << COUNT_BITS;

执行流程

通过源码可以发现ThreadPoolExecutor->AbstractExecutorService->ExecutorService->Executor,Executor是线程池的顶级接口,它只有一个方法execute(),定义了线程池的执行流程:
  1. 首先判断当前线程数是否小于核心线程数,小于的话addWorker()创建核心线程执行任务;
  2. 如果线程池中的线程数量已经超过核心线程数,这时候任务就会被插入到任务队列中排队等待执行;
  3. 如果任务队列已满,这个时候如果线程池中的线程数量没有达到线程池所设定的最大值,那么这时候就会addWorker()创建一个非核心线程来执行任务;
  4. 如果线程池中的数量达到了所规定的最大值,那么就会执行拒绝策略。
public void execute(Runnable command) {
     if (command == null)
        throw new NullPointerException();
     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)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
}

创建线程

从上一节可以看出,创建核心和非核心线程都是调用的addWorker()方法,该方法分为两个部分,第一部分是增加线程池中线程的数量,首先判断线程池的状态,如果为特殊状态返回false,否则通过CAS操作添加线程数量, 如果成功添加,则通过goto语句跳出当前的两个死循环,执行之后的语句,如果cas操作失败,继续循环尝试。
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 &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

如果第一部分执行成功,那么已经修改ctl(线程池数量),第二部分就是对worker具体添加过程的实现。 添加成功后直接执行worker的内置线程,即开始运行firstTask;若添加失败,则执行addWorkerFailed(),这个方法就是在addWorker添加失败后对之前的操作执行撤回的一个方法, remove掉之前添加的worker,接着调用decrementWorkerCount(),可以理解为撤销addWorker这个方法第一部分的操作。

        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);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
}

执行任务

addWorker添加完worker后会直接启动worker,首先会通过run方法执行firstTask,执行完毕后会将task置为null,那么task != null的判断条件肯定不通过,它就会尝试通过getTask(),从任务队列中获取任务。
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 {
            processWorkerExit(w, completedAbruptly);
        }
}

getTask()方法:

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())) {
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);

            // Are workers subject to culling?
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

            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;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

getTask()方法在两种情况下会返回null,一是线程池状态为特殊状态;二是当前线程的数量不满足创建线程池时设置的参数。第一种比较简单,这里主要分析第二种情况:

  1. wc > maximumPoolSize || (timed && timedOut)
    如果当前线程数量大于线程池的最大线程数量(为什么当前线程数可能会大于最大线程数呢?官方给了解释,事实上的线程数最大值是由CAPACITY决定的),或者说允许销毁标记timed和超时标记timedOut均为true,该条件满足。只有当线程池不允许核心线程被回收且当前线程数量不大于核心线程数时,Timed才会为false。
  2. wc > 1 || workQueue.isEmpty())
    如果当前线程数量大于1,或者任务队列为空,该条件满足。在1、2两个条件均满足时,返回null。

总结:若当前线程数已经大于最大线程数,那么就一定会返回null。如果当前线程数大于核心线程数,小于最大线程数(也就是说这个线程是非核心线程),这时timed = true,但由于timedOut初始为false,所以第一个条件不满足,整体也为false,代码继续往下执行,这时会根据timed为true或者false,执行r = workQueue.poll()或者workQueue.take(),这两个函数的区别是,poll()执行后得到的r = null,所以timedOut被标记为false,然后进入下一次循环,但是这次timed与timedOut均为true,getTask()就会返回null;timed为false时执行take(),这个函数会将所有的核心线程卡在这里(如果核心线程允许超时回收,这里只会卡一个,其他都返回null然后被销毁),不停的尝试获取任务,直到有任务出现,再把任务分给线程。可以理解为非核心线程执行完自己的firstTask后,是无法通过getTask()拿到队列中的任务的,但是若设置了允许核心线程超时回收那么即使当前线程数没有达到核心线程数,getTask依旧会返回null,保底只维持一个线程执行任务。

销毁线程

线程通过循环尝试从任务队列取任务,但是当getTask()取任务失败之后会执行finllay里的processWorkerExit()方法,该方法是线程池中worker的回收机制实现。 首先加锁,把当前worker从workers中remove,接着又判断线程池状态,根据是否允许核心线程超时被回收,min会被赋0(若任务队列不为空则设置为1,线程进入到processWorkerExit()最可能的情况就是任务队列已经空了跳出了runWorker的循环,而这里判断时队列不为空应该是新添加进了任务,那么线程池就会留一个线程去执行这个任务)或corePoolSize(允许的核心线程最大数量)。然后判断当前线程数是否超过要求的数量,如果超过就直接返回,否则创建一个新线程维持线程的数量(因为一上来就先销毁了)。
private void processWorkerExit(Worker w, boolean completedAbruptly) {
        if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
            decrementWorkerCount();

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            completedTaskCount += w.completedTasks;
            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; // replacement not needed
            }
            addWorker(null, false);
        }
}

总结

线程池中线程在回收线程的时候首先通过getTask方法建议回收,如果允许核心线程超时被回收那么在任务队列里已经没有任务,即线程进入超时状态后,核心线程执行完任务,getTask只会保留一个线程工作,其他worker都会被返回null,在runWorker方法中跳出while循环,执行后面的procrsworkerexit回收机制,processWorkerExit则会具体执行回收机制,再次通过是否允许回收超时核心线程这一参数来决定通过addworker维持的线程池线程数量,如果允许被回收,那么仅维持一个,如果不允许被回收,那么维持corePoolSize个线程。

参考:
https://www.jianshu.com/p/8089ceb980ab
https://blog.csdn.net/leandzgc/article/details/103111658
https://blog.csdn.net/qq_39176007/article/details/115616106

posted @ 2022-06-14 14:51  学海无涯#  阅读(137)  评论(0)    收藏  举报