java源码阅读之ThreadPoolExecutor

概述

先看一下ThreadPoolExecutor的继承关系:
这里写图片描述

线程池ThreadPoolExecutor主要用来解决两个问题:

  • 通过减少每个任务的调度开销,从而提高大量异步任务执行效率
  • 提供了一种管理和限制资源(如线程)的方法

此外线程池也提供给了一些基本的统计信息,如完成的任务数等。线程池有以下好处:

  • 降低资源消耗:通过重用已经创建的线程来降低线程创建和销毁的消耗
  • 提高响应速度:任务到达时不需要等待线程创建就可以立即执行。
  • 提高线程的可管理性:线程池可以统一管理、分配、调优和监控。

ThreadPoolExecutor提供了很多自适应的参数和可扩展的hooks,但JUC包也停工了更为简洁的类ExecutorsExecutors提供了newFixedThreadPool(int nThreads)固定线程池,newSingleThreadExecutor()单线程线程池,newCachedThreadPool()无限大小线程池等工厂方法来创建各种常见的线程池。主意某些编码规范中不推荐使用Executors,而是使用ThreadPoolExecutor,因为这样可以加深对线程池原理的理解。

核心参数解读

Core and maximum pool sizes

线程池中的线程数目会根据corePoolSizemaximumPoolSize两个参数来自动调整。当一个新的任务经由execute方法提交后,如果当前运行的线程数小于corePoolSize,即使其它工作线程空闲,也会新创建一个线程来处理这个任务。如果当前运行线程在corePoolSizemaximumPoolSize之间,只有当队列(queue)满时,才会创建新的线程处理任务。通过设置相同的corePoolSizemaximumPoolSize,可以创建一个固定大小的线程池(与Executors.newFiexedThreadPool()类似),通过设置maximumPoolSize到一个无限制的值,如Integer.MAX_VALUE,就可以创建一个容许任意数目并发任务的线程池。
通常,这两个参数在ThreadPoolExecutor对象构建的时候,但也可以通过setCorePoolSize(int)setMaximumPoolSize(int)方法动态修改。

On-demand construction

默认情况下,核心线程在线程池初始化的时候创建,在新的任务提交后启动。可以通过方法prestartCoreThread()prestartAllCoreThreads()动态覆盖,比如如果使用非空队列构建线程池,就可能需要预先启动核心线程。

创建新线程

新线程通过ThreadFactory创建,如果构建线程池的时候没有指定ThreadFactory,默认使用Executors.defaultThreadFactory()创建新线程,而且所有其创建的线程都在同一个ThreadGroup下,优先级都为NORM_PRIORITY,都是非守护线程。如果自己指定ThreadFactory,就可以自定义线程组,线程名称,优先级,守护状态等参数。

Keep-alive times

如果线程池内的线程数大于corePoolSize,则大于corePoolSize的线程在空闲后的keepAliveTime时间后,会自动终结,这样做是为了减小有空闲线程时资源消耗。这个参数也可以通过方法setKeepAliveTime(long, TimeUnit)动态设置。通常,keepAliveTime参数只对大于corePoolSize的额外线程有效,但是如果设置了allowCoreThreadTimeOut(boolean),可以将这个超时时间也作应用于核心线程。

Queuing

任何阻塞队列BlockingQueue都可以被线程池用来传输和暂存提交的任务。队列的使用与线程的大小相关:

  • 如果工作线程数小于corePoolSize,执行程序通常会创建线程,而不是将新任务放进队列排队
  • 如果工作线程大于等于corePoolSize,执行程序通常会将新线程放进队列排队,而不是创建新线程执行任务
  • 如果新的任务请求无法排队,会创建新的线程只到工作线程达到maximumPoolSize,如果工作线程大于maximumPoolSize,则这个任务提交请求将会被拒绝(详见拒绝策略)。

队列常见的有三种类型:

  • 直接递交转发,即直接将任务传递给工作线程,不会暂存任务。SynchronousQueue队列就是这种队列。这种队列通常需要设置无限制的maximumPoolSize,使得线程池不会拒绝新任务的提交请求。但是这样使得如果任务处理速度达不到提交速度时,线程池工作线程可能无限增长。
  • 无限队列,如一个未预设容量的LinkedBlockingQueue,无限队列使得所有的新任务在核心线程满的时候进入队列排队。此时不会又超过corePoolSize的线程被创建,所以这是的maximumPoolSize的设置不会起作用。这种队列适用于所有的任务相互独立,并且任务的执行不会相互影响。这种队列可以平滑的消除瞬间大量并发的请求,但也会导致队列无限增长。
  • 有限队列,如ArrayBlockingQueue。有限队列加上有限的maximumPoolSize可以阻止资源耗尽,但是却难以调度和控制,大队列和小池可以减少cpu的使用、上下午的调度切换等,但是却降低了系统的吞吐量;当如果使用小队列和大池,可能会使得cpu一直忙碌,但是也增加了cpu切换的开销,也可能降低吞吐量。

Rejected tasks

如果执行程序关闭,或者有限池和有限队列满时,新提交的任务将会被拒绝。无论哪种情况,RejectedExecutionHandler会调用RejectedExecutionHandler.rejectedExecution(Runnable, ThreadPoolExecutor)方法来处理拒绝策略,ThreadPoolExecutor内置了四种拒绝策略:

  • 默认的拒绝策略是ThreadPoolExecutor.AbortPolicy,其拒绝方式是直接抛出运行时异常RejectedExecutionException
  • ThreadPoolExecutor.CallerRunsPolicy,这种策略直接调用线程的execute方法来运行这个任务,这种策略降低了线程池的提交速度,是一种简单的反馈控制机制。
  • ThreadPoolExecutor.DiscardPolicy策略,这种策略简单的丢弃这个新提交的任务。
  • ThreadPoolExecutor.DiscardOldestPolicy策略,这种策略适用于线程池满的情况,不适用于执行程序关闭的情况。其做法是丢弃队列头部的任务。
    当然我们也可以使用自定义的RejectedExecutionHandler类。需要注意的是有些策略仅仅在有限队列和有限池的情况下工作。

Hook methods

ThreadPoolExecutor提供了protectedbeforeExecute(Thread, Runnable)afterExecute(Runnable, Throwable)供自定义重载。这两个方法分别在任务执行前后执行,可以用来准备任务执行环境等。如初始化threadLocal变量,统计或者添加日志等。
如果挂钩方法或者回调方法执行异常,则工作线程也会终止。

关键方法\属性\算法

AtomicInteger ctl

用来记录当前线程池的线程数workerCount和线程池的状态runState。其中低29位是workerCount,高三位是runState,其中线程池状态枚举值包括:

// runState is stored in the high-order bits
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;

然后通过内部静态方法分别取出ctl中的workerCountrunState

// Packing and unpacking ctl
private static int runStateOf(int c)     { return c & ~CAPACITY; }
private static int workerCountOf(int c)  { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }

BlockingQueue<Runnable> workQueue

用来缓存和传送任务的队列,不需要通过workQueue.poll() == null来判断队列是否为空,而是根据workQueue.isEmpty()判断即可。

ReentrantLock mainLock

主锁,取得锁方可访问线程集合(workers set)。

HashSet<Worker> workers

线程集合,用来容纳池内所有线程。只有获得mainLock才可以访问workers

构造方法

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.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

具体参数不再详解,参考关键属性

public void execute(Runnable command)

execute()方法用于线程提交。具体实现:

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

不难看出其处理流程:

  1. 当前工作线程数小于corePoolSize,新提交任务时,线程池会试图创建新的线程,并把这个任务作为其第一个任务。
  2. 否则将试图将当前线程入队列,如果入队列成功还需要重新检查进入本分支后,线程池是否shutdown,如果线程池shutdown,就从队列中移除当然任务,然后拒绝这个任务提交,否则在工作线程为0的情况下,创建新的线程(并没有将新的这个线程作为第一个任务)。
  3. 如果任务入队列失败,则尝试创建新的线程,创建失败则说明了当前线程池的状态变化或者线程池已满,接下来就会拒绝这个任务提交。

核心方法addWorker()

addWorker方法首先会检查线程池的运行状态,判断过程:

  • 如果线程池状态rs为-1(即RUNNING),则先决条件判断通过
  • 如果线程池状态rs>=SHUTDOWN,则进行后续判断
    • 如果线程池状态rs>SHUTDOWN,即线程池处于STOP,TIDYING或者TERMINATED状态,直接返回false
    • 如果线程池状态rs==SHUTDOWN,即线程池处于SHUTDOWN状态,进行后续判断,继续判断
      • 如果此时fisrtTask不为空,即线程池处于SHUTDOWN状态,还有新的任务提交,则直接拒绝该任务提交
      • 如果firstTask为空,判断阻塞队列为空,即线程池处于SHUTDOWN状态,而且阻塞队列为空,也没有新任务提交,无需创建线程,返回false,否则表示队列还有任务未执行,可以创建新任务,判断条件通过

如果上述先决条件判断通过,则判断工作线程数是否达到允许最大值((1 << 29) - 1),或者工作线程大于corePoolSize(添加核心线程)或者最大线程maxPoolSize(添加非核心线程),直接返回false,新线程创建失败。线程池工作线程数判断条件通过,则尝试对线程数经常cas加一,如果工作线程加一操作成功,跳出循环,进行创建线程操作。否则判断当前线程池状态rs,如果此时rs和进入方法时的状态变化,则重复上述全部判读。

进入创建线程的流程,创建Worker的实例,用来容纳任务。然后获取mainlock的锁,获取锁后,进一步检查当前线程池的运行状态,如果线程池处于RUNNING状态或者线程池SHUTDOWN而且firstTask为空时,先检查当前任务是否可运行,如果可运行,将新创建的worker添加到工作线程集和workers中,否则抛出异常。添加任务到workers集合后,修改largestPoolSize。如果上述一切顺利,启动线程执行任务:t.start();,否则执行addWorkerFailed,回滚工作线程数workCount。

    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))
                    //workCount cas +1 success,break the loop and start to create new thread
                    break retry;
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    //runStat changed since enter this method, recheck all above。
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

        //若进行到了此步操作,则表明工作线程数量加了1
        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)
                //rolling back workCount
                addWorkerFailed(w);
        }
        return workerStarted;
    }

final void runWorker

上一节中,

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值