ThreadPoolExecutor源码解析

ThreadPoolExecutor中有如下一段注释
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn’t, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*

原文意思大致如下:
1、如果少于corePoolsize的线程正在运行,尝试为给定的新任务创建一个新的线程去执行该任务。在创建新的线程的时候会去自动检查当前线程池的运行状态和当前处于运行状态的线程数,如果当前不需要添加新的线程时,会返回false。
2、如果一个任务可以成功的被加入到任务队列中,我们仍然需要再次校验一下是否需要新建一个线程(因为有可能当我们进入这个方法的时候线程池被其他线程关闭)。所以我们需要重新校验一下线程池运行状态,如果线程池被关闭,我们甚至有必要去回滚入队操作。
3、如果我们无法将当前任务放入到任务队列中,我们需要尝试去创建一个新的线程。如果无法创建新的线程,那么可能是线程池被关闭或者当前运行线程数超过了线程池设定的最大运行线程数,我们将拒绝这个任务。

我们用自己的话去理解一下上面这段话,大致如下:
1、当线程池被初始化的时候,一般默认运行线程数为0。当有新任务被提交到线程池时(当前线程池运行线程数小于corePoolSize),我们会创建一个新的线程去执行该任务。
2、如果当前线程池中正在运行的线程数大于corePoolSize,我们将会把新提交的任务放入任务队列中以等待后执行。
3、如果任务无法入队,可能是线程池被停止或者当前已满。如果当前线程池被关闭,我们需要根据一定的策略来处理当前任务。如果任务队列已满,我们将会尝试创建新的线程去执行当前提交的任务。
4、如果当前队列已满同时当前线程池中正在运行的线程数大于线程池设定的maximumPoolSize数量,则拒绝新提交的任务。

在线程池的设计中,corePoolSize和maximumPoolSzie这两个概念非常的关键,corePoolsize用于定义线程池中核心线程数量,maximumPoolSize用于定义线程池中最大线程池数量。了解了这两个变量的定义对于我们理解线程的设计与实现十分有帮助。

下面我们结合ThreadPoolExecutor的源码来理解线程池的实现。
在创建了线程池后,线程池中的线程数默认数量为0,当有任务进入线程池后线程池会新建一个线程用于执行该任务。如果线程池中执行中线程数量大于corePoolsize后,则将新接收到的任务添加到任务队列中等待执行。

public void execute(Runnable command) {  
    if (command == null)  
        throw new NullPointerException();  
    if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {    
        if (runState == RUNNING && workQueue.offer(command)) {  
            if (runState != RUNNING || poolSize == 0)    
                ensureQueuedTaskHandled(command);  
        }   
        else if (!addIfUnderMaximumPoolSize(command))  
            reject(command); // is shutdown or saturated  
    }  
}

当任务被提交到线程池后,线程池会调用execute()方法来执行任务。下面一句一句来分析execute()方法代码:

if (command == null)  
        throw new NullPointerException();

如果当前任务为null,抛出空指针异常。

if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) { 

如果当前线程池中正在运行的线程数量大于核心线程池数量,或者addIfUnderCorePoolSize返回的为false,则执行if包裹的代码段。addIfUnderCorePoolSize()方法的功能是:如果当前线程池中正在运行的线程数量小于corePoolSize,线程池会新建一个线程用于执行当前提交的任务。如果线程创建成功,addIfUnderCorePoolSize()方法会返回true,如果当前线程池中处于运行状态的线程数大于corePoolSize或者线程池处于关闭状态则会返回false。代码如下:

    private boolean addIfUnderCorePoolSize(Runnable firstTask) {
        Thread t = null;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            if (poolSize < corePoolSize && runState == RUNNING)
                t = addThread(firstTask);
        } finally {
            mainLock.unlock();
        }
        if (t == null)
            return false;
        t.start();
        return true;
    }

如果调用addThread()方法后成功创建了一个线程用于执行当前任务,会调用t.start()方法执行该任务。

if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) { 

回顾一下上面的判断逻辑,如果线程池当前线程数量数量大于corePoolSize或者addIfUnderCorePoolSize()方法返回false,执行下面判断。

if (runState == RUNNING && workQueue.offer(command)) {

能执行到这一步,基本上意味着当前线程池中正在执行的线程数量大于corePoolSize。如果此时线程池的状态为RUNNING,并且我们将可以成功将任务添加到工作者队列中(workQueue),则执行下一步逻辑。

if (runState != RUNNING || poolSize == 0)    
                ensureQueuedTaskHandled(command); 

既然上面我们已经判断了线程池处于运行状态,而且任务可以成功的被添加到工作队列中。那为什么这里还要再校验一次线程池的运行状态呢?因为当我们无法保证此刻会不会有其他线程终止当前线程,我们需要有一定的机制来处理这种情况。这里提供了处理方法。ensureQueuedTaskHandled()

private void ensureQueuedTaskHandled(Runnable command) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        boolean reject = false;
        Thread t = null;
        try {
            int state = runState;
            if (state != RUNNING && workQueue.remove(command))
                reject = true;
            else if (state < STOP &&
                     poolSize < Math.max(corePoolSize, 1) &&
                     !workQueue.isEmpty())
                t = addThread(null);
        } finally {
            mainLock.unlock();
        }
        if (reject)
            reject(command);
        else if (t != null)
            t.start();
    }

当任务被放入任务队列后,如果线程池状态被改变时会调用该方法。这个方法的作用是:如果线程被放入队列后,其它线程关闭了该线程池,使得当前线程池运行状态为关闭,我们需要将该任务移出任务队列,此时线程池会拒绝该任务的执行。如果当前线程池状态仍处于运行状态且线程池poolsize小于corePoolSize,则新建一个线程用于帮助线程池执行未处理的任务。

回顾一下上面的代码

public void execute(Runnable command) {  
    if (command == null)  
        throw new NullPointerException();  
    if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {    
        if (runState == RUNNING && workQueue.offer(command)) {  
            if (runState != RUNNING || poolSize == 0)    
                ensureQueuedTaskHandled(command);  
        }   
        else if (!addIfUnderMaximumPoolSize(command))  
            reject(command); // is shutdown or saturated  
    }  
}

此时我们需要关注的是

    else if (!addIfUnderMaximumPoolSize(command))  
        reject(command); // is shutdown or saturated  

addIfUnderMaximumPoolSize()方法会判断当前线程池任务队列是否已满以及线程池中正在运行的线程数是否大于maximumPoolSize。如果队列已满且线程池poolSize大于maximumPoolSize,则拒绝当前任务,执行reject()方法。如果当前线程池中的工作队列已满,且poolSize小于maximumPoolSize,线程池会新建一个任务帮助线程池处理剩余的任务。

private boolean addIfUnderMaximumPoolSize(Runnable firstTask) {
    Thread t = null;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        if (poolSize < maximumPoolSize && runState == RUNNING)
            t = addThread(firstTask);
    } finally {
        mainLock.unlock();
    }
    if (t == null)
        return false;
    t.start();
    return true;
}
private Thread addThread(Runnable firstTask) {
        Worker w = new Worker(firstTask);
        Thread t = threadFactory.newThread(w);
        if (t != null) {
            w.thread = t;
            workers.add(w);
            int nt = ++poolSize;
            if (nt > largestPoolSize)
                largestPoolSize = nt;
        }
        return t;
    }

到目前为止,我们已经知道了线程池什么时候会去新建线程,以及何时会将任务放入到任务队列中。那么放入到任务队列中的任务何时会被执行呢?这个秘密就藏在t.start();这句代码中!
当我们调用addThread方法为线程池成功添加线程,会执行t.start()方法,实际上执行的就是worker的run方法。

public void run() {
    try {
        Runnable task = firstTask;
        firstTask = null;
        while (task != null || (task = getTask()) != null) {
            runTask(task);
            task = null;
        }
    } finally {
        workerDone(this);
    }
}

run方法中最核心的地方就是while循环,当task不为空时,会直接跳到runTask()方法中执行当前任务。任务执行完毕后,task对象被置为空,继续跳转到while判断条件中,调用getTask()方法来获取新的任务。

Runnable getTask() {
        for (;;) {
            try {
                int state = runState;
                if (state > SHUTDOWN)
                    return null;
                Runnable r;
                if (state == SHUTDOWN)  // Help drain queue
                    r = workQueue.poll();
                else if (poolSize > corePoolSize || allowCoreThreadTimeOut)
                    r = workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS);
                else
                    r = workQueue.take();
                if (r != null)
                    return r;
                if (workerCanExit()) {
                    if (runState >= SHUTDOWN) // Wake up others
                        interruptIdleWorkers();
                    return null;
                }
                // Else retry
            } catch (InterruptedException ie) {
                // On interruption, re-check runState
            }
        }
    }

    private boolean workerCanExit() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        boolean canExit;
        try {
            canExit = runState >= STOP ||
                workQueue.isEmpty() ||
                (allowCoreThreadTimeOut &&
                 poolSize > Math.max(1, corePoolSize));
        } finally {
            mainLock.unlock();
        }
        return canExit;
    }

我们来分析一下上面这段代码干了什么。
首先取得当前线程池的工作状态并赋值给state,
1、如果当前状态大于SHUTDOWN,则返回null。
2、如果当前线程池状态为SHUTDOWN,,调用poll方法取出任务队列中的第一个任务(如果任务队列没有任务,poll会返回null)。
3、如果当前线程池中正在运行的线程数量大于corePoolSize,同时允许线程池中线程空闲一段时间后被关闭,则调用workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS);获取工作队列中的任务。
4、其余情况则直接从任务队列中取一个任务返回。
如果我们获取到的Runnable对象为不为空,则返回该对象。
workerCanExit方法的意思是,如果当前线程池被关闭,或工作队列为空或者线程池允许空闲线程被终止,前提是线程池中有足够的线程数执行剩余的任务,则返回null,以便使得当前工作线程被销毁。

至此为止,ThreadPoolExecutor线程池如何创建线程并执行被提交的任务,以及线程池如何控制线程数量用于执行任务已经有了一个大致的了解。欢迎指正和批评。

下一次我们将介绍ThreadPoolExecutor中的ReentrantLock锁。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值