java线程池ThreadPoolExecutor源码解析

本文深入探讨了线程池的工作原理及其核心组件ThreadPoolExecutor的构造函数参数意义,详细解析了线程池的状态转换机制和任务执行流程,同时对比了线程池关闭方法shutdown()与shutdownNow()的区别。
摘要由CSDN通过智能技术生成

前言

线程池是我们工作中经常会用到的,但是能高效、准确地使用它却不容易。下面会通过原理和源码来分析线程池的工作原理。

ThreadPoolExecutor原理分析

ThreadPoolExecutor构造函数参数说明

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        // 两种抛出异常的情况
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

下面对每个构造函数的参数功能给出结论,后面会进行分析

  • corePoolSize : 核心线程数,也就是线程池能一直保留的线程数,即使这些线程使用完毕,也会保存,以供复用。因此此变量不宜过大,因为过大不仅会耗费资源,更重要的是cpu调度不过来,会造成线程执行卡顿。建议数量不要超过cpu核数的两倍。
  • workQueue : 存放任务的队列,当线程池需要执行的线程数,超过了corePoolSize,就会将任务放到这个队列里面。
  • maximumPoolSize : 最大线程数,也就是当前线程池可以创建的最大线程数,当需要调度的任务超过了workQueue最大数目,就会检查当前线程数是否达到了maximumPoolSize,如果没有,则直接创建新的线程执行;否则,就会抛出异常,处理异常的策略是根据handler决定的。
  • keepAliveTime : 超过corePoolSize的线程空闲时,能存活的时间
  • unit : keepAviveTime的单位
  • threadFactory : 创建线程的工厂,用于创建线程。
  • handler : 创建线程失败的处理策略

ThreadPoolExecutor 源码解析

线程池状态解析

首先,需要先了解记录当前线程池的状态和正在运行的线程数的变量,其实它是通过一个变量来记录两个数据的,这个变量是ctl

// 记录线程池的状态和正在运行的线程数目,全局都会用到此变量,很重要
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; // 调用了shutDown()方法,但是此状态不再接收新的任务
    private static final int STOP       =  1 << COUNT_BITS; // 调用了shutDownNow()方法的状态
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;

    // 获取线程池的状态api
    private static int runStateOf(int c)     { return c & ~CAPACITY; }
    // 获取正在运行的线程数api
    private static int workerCountOf(int c)  { return c & CAPACITY; }
    // 创建ctl的api
    // 参数: rs:线程池的状态; wc: 正在运行的线程数目
    private static int ctlOf(int rs, int wc) { return rs | wc; }

通过上面,我们可以线程池是通过一个int型变量来存储运行状态和运行的线程数;int是32位,它用前三位存储线程池的状态,后29位存储线程的数目。
优点: 1. 减低内存的使用 2. 只需要保证一个变量的线程安全,效率提高

线程池运行解析

接着从execut()方法看起

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

        int c = ctl.get(); // 存储线程池状态和线程数的变量
        // 判断当前运行的线程数是否超过 corePoolSize
        if (workerCountOf(c) < corePoolSize) {
            // 小于corePoolSize,直接创建线程,addWorker()内部会再一次检验线程数是否超过corePoolSize
            if (addWorker(command, true))
                return;
            // 创建线程失败,重新获取clt,进行下面操作
            c = ctl.get();
        }
        // 如果当前线程池正在运行,并且将task添加到队列中
        if (isRunning(c) && workQueue.offer(command)) {
            // 双层校验,第二次简要clt
            int recheck = ctl.get();
            // 如果当前线程池没有在运行,从队列中删除任务,并调用失败拒绝策略
            if (! isRunning(recheck) && remove(command))
                reject(command);
            // 如果正在运行的线程数是0(也就是corePoolSize为0的情况)
            else if (workerCountOf(recheck) == 0)
                // 检验当前线程数是否大于maxPoolSize,如果小于,从任务队列中获取任务,创建线程
                addWorker(null, false);
        }
        // 校验当前线程数是否大于maxPoolSize, 如果小于,直接创建线程执行当前任务。
        else if (!addWorker(command, false))
            reject(command);
    }

下面来看addWorker()方法执行流程

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
            }
        }

        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;
    }

先给出addWork()实现的逻辑的总结:
参数core:true:证明创建的core线程,所以当前线程数需要小于corePoolSize; false:创建的超过core数目的线程(这些在空闲keepAliveTime之后会被回收),需要判断当前线程数是否小于maxPoolSize,小于才能创建线程。
参数firstTask:不为null,创建的线程执行的task就是firstTask;为null,创建的线程执行的是queue里面的task(注意这个时候是复用的core线程)。
核心内容下面继续分析。

下面看Worker源码,它继承Runnable,创建的线程执行的它的run()方法;它继承自AbstractQueuedSynchronizer,也就是它是一个锁,是为了保证外部不同线程对此线程的操作保证线程安全的作用。

    private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {

        final Thread thread;
        /** Initial task to run.  Possibly null. */
        Runnable firstTask;
        /** Per-thread task counter */
        volatile long completedTasks;

        // 构造函数
        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this); // 这个Thread的Runnable是当前对象,因此线程执行的时候,执行的当前对象的run()方法
        }

        /** Delegates main run loop to outer runWorker. */
        public void run() {
            //调用的是ThreadPoolExcutor的runWorker()方法
            runWorker(this);// 传入的参数是当前对象
        }
    }
线程的复用和回收

接着上面的分析继续,
下面来看核心的调度queue中task的方法runWorker():

    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            // 核心:如果task不为null,那么会直接执行当前task;如果task为null,调用getTask()从队列中取task
            // 并且当getTask()从queue中取不到task,将终止当前循环
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // 这里会判断当前状态是否大于STOP(也就是stop以上的状态),如果是,也就会终止当前线程。这也就是shutDownNow()的特点(shutDownNow()会将线程池置为STOP状态)
                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);
        }
    }

复用就体现在上面:上面runTasks()方法一直泡在一个线程里面,执行完一个task,就会从队列中取另一个task,只有当取不到task,当前线程处于闲置。这样task之间就可以复用线程。我们也可以得知复用线程就是将task在同一个线程里面执行。
而大于corePoolSize会被回收,如何实现,是通过getTask()实现的;通过上面的runWorker()可以得知getTask()取不到task,那么循环将会终止,,也就是当前线程将会终止。

    private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

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

            // 如果当前状态是SHUTDOWN,并且task阻塞队列为空;或者当前状态为STOP,将会停止当前线程,并且将记录的线程数目减一
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);

            // 记录当前线程是否可以被关闭,allowCoreThreadTimeOut默认为false,可以通过allowCorThreadTimeOut(true)来设置
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
            // timedOut是指当前线程闲置的时间是否到达keepAliveTime,如果达到,则置为true
            // 如果当前线程数大于maximumPoolSize;或者当前线程数量可以清除当前线程并且闲置时间超过keepAliveTime,并且当前运行线程数大于1个或者等待task队列为空。。尝试关闭当前线程
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                // 降低线程数(条件是状态和数目都和现在相同)
                if (compareAndDecrementWorkerCount(c))
                    // return null 表示关闭线程
                    return null;
                continue;
            }

            try {
                // 如果符合可以进行删除线程的条件,那么会keepAliveTime时间内从队列中获取task;否则直接获取,获取不到,阻塞线程,直到有数据添加,或者调用interrupt()终止当前线程。
                // 总结:如果线程数在corePoolSize范围内,那么如果workQueue(task阻塞队列)没有task,那么会一直阻塞线程,直到有task,因此得出corePoolSize范围内的线程不会被销毁;如果当前线程数大于corePoolSize,那么在keepAliveTime时间内没有task执行(也即是闲置),会将当前线程关闭。(以上结论均在allowCoreThreadTimeOut为false(默认值)的情况)
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                // 如果获取到,直接返回;获取不到,继续循环获取
                if (r != null)
                    return r;
                // 兵器timeout为true了,也就是keepAliveTime时间到了
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

通过上面注释解析,可以得知,线城池会保留corePoolSize(没有将allowCoreThreadTimeOut置为true)的线程,如果大于此数量的线程,如果闲置时间超过keepAliveTime会进行停止线程的操作。

线程的关闭

下面来看看shutDown()方法,作用:将线程池置为SHUTDOWN状态,此时线程池不再接受新的线程,但是会执行完task队列中剩余的task;并且按顺序关系正在执行的线程。重复调用shutDown()不起作用
注意:shutDown()的停止线程,是通过Thread.interrupt()实现的,因此只能关闭那些通过sleep()、join()、wait()等造成阻塞的线程

public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            // 校验是否进行shutDown()操作
            checkShutdownAccess();
            // 修改运行状态未SHUTDOWN
            advanceRunState(SHUTDOWN);
            // 当线程停止的时候,终止线程
            interruptIdleWorkers();
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }

和shutDown()相区别的是:shutDownNow(),关闭线程不是按照顺序执行的,并且会返回正在等待的task,此种状态不再接收任何外部源码如下:

public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            // 修改当前运行状态为STOP
            advanceRunState(STOP);
            // 中断所有正在工作的线程
            interruptWorkers();
            // 返回正在等待的task
            tasks = drainQueue();
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        return tasks;
    }

可以看出他们的核心区别在interruptIdleWorkers()和interruptWorkers(),下面来看看他们实现的区别。

    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;
                // tryLock()保证多线程调用的时候不会重复执行,并且不会改变shutdown的顺序。
                if (!t.isInterrupted() && w.tryLock()) {
                    try {
                        t.interrupt();
                    } catch (SecurityException ignore) {
                    } finally {
                        w.unlock();
                    }
                }
                if (onlyOne)
                    break;
            }
        } finally {
            mainLock.unlock();
        }
    }
private void interruptWorkers() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers)
                w.interruptIfStarted();
        } finally {
            mainLock.unlock();
        }
    }
    void interruptIfStarted() {
            Thread t;
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                }
            }
        }

可以看出interruptWorkers()没有做线程安全控制。不同多次调用可能会出现关闭线程的次序随机。
可以看到shutDown()和shutDownNow()方法均调用了tryTerminate()方法,而这个方法被多个地方调用了,比如task的remove()。它主要作用是将线程池的状态从SHUTDOWN->TIDYING,TIDYING->TERMINATE(需要满足转化状态的条件)转化的实现,代码如下:

    final void tryTerminate() {
        for (;;) {
            int c = ctl.get();
            // 下面三种条件不能进行状态的转化
            // 1. 当前状态是RUNNING
            // 2. 当前状态大于等于TIDYING状态(也就是TIDYING和TERMINATE)
            // 3. 当前状态是SHUTDOWN,但是task等待队列不为空
            if (isRunning(c) ||
                runStateAtLeast(c, TIDYING) ||
                (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
                return;
            // 如果执行到这里的条件是,当前线程池的状态是SHUTDOWN状态,并且task等待队列为空;或者当前状态是STOP状态
            // 如果当前运行的线程没有完全停止,也不满足状态转化的条件
            if (workerCountOf(c) != 0) { // Eligible to terminate
                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();
            }
            // else retry on failed CAS
        }
    }

总结

线程池工作流程:

  1. 先判断当前运行线程数是否小于corePoolSize,小于则直接创建线程运行当前task
  2. 如果大于等于corePoolSize,将当前task添加到task队列中,如果添加成功,如果当前线程池状态不是运行状态,从队列中删除当前task,并出发reject策略;如果当前运行线程数为0(对应corePoolSize为0),直接运行当前task
  3. 如果task添加到task队列失败,如果当前线程数小于maxPoolSize,直接运行当前线程
  4. 执行线程的方法是runWorker(),此方法会循环从task队列中获取task执行,也就是task复用线程,直到task队列中取不到task。

线程池的几种状态:

  • RUNNING : 默认状态,也就是线程池正在运行
  • SHUTDOWN : 调用shutDown()后的状态,进入此状态之后;不再接收新的task(可以查看上面execute()方法内容可知),但是task队列中等待的task会执行完成(runWorker()函数没有对SHUTDOWN状态做拦截,因此还是会继续执行遍历task queue后执行)
  • STOP:调用shutDownNow()后的状态,进入此状态之后;不再接收新的task(同样execute()方法可知),并且也不会再执行task队列中剩余的taks(同样可以通过runWorker()函数可知)
  • TIDYING:条件:状态为SHUTDOWN,并且当前没有正在运行的线程,正在等待的task队列为空;或者当前状态是STOP,并且正在等待的task队列为空
  • TERMINATE:进入到TIDYING之后,执行完terminate()之后的状态。
    官方注释说明(jdk8.0):
    /* The runState provides the main lifecycle control, taking on values:
     *
     *   RUNNING:  Accept new tasks and process queued tasks
     *   SHUTDOWN: Don't accept new tasks, but process queued tasks
     *   STOP:     Don't accept new tasks, don't process queued tasks,
     *             and interrupt in-progress tasks
     *   TIDYING:  All tasks have terminated, workerCount is zero,
     *             the thread transitioning to state TIDYING
     *             will run the terminated() hook method
     *   TERMINATED: terminated() has completed
     *
     * The numerical order among these values matters, to allow
     * ordered comparisons. The runState monotonically increases over
     * time, but need not hit each state. The transitions are:
     *
     * RUNNING -> SHUTDOWN
     *    On invocation of shutdown(), perhaps implicitly in finalize()
     * (RUNNING or SHUTDOWN) -> STOP
     *    On invocation of shutdownNow()
     * SHUTDOWN -> TIDYING
     *    When both queue and pool are empty
     * STOP -> TIDYING
     *    When pool is empty
     * TIDYING -> TERMINATED
     *    When the terminated() hook method has completed
     *
     * Threads waiting in awaitTermination() will return when the
     * state reaches TERMINATED.
     *
     * Detecting the transition from SHUTDOWN to TIDYING is less
     * straightforward than you'd like because the queue may become
     * empty after non-empty and vice versa during SHUTDOWN state, but
     * we can only terminate if, after seeing that it is empty, we see
     * that workerCount is 0 (which sometimes entails a recheck -- see
     * below).
     */
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值