【Java线程池详解】

线程池介绍

在日常的工作中,无论是开发工作还是测试工作,都会有使用线程池的场景,比如web后端开发中用多线程处理请求,在测试中我们有时候需要以多线程的方式准备测试数据,甚至很多压测工具如jmeter都是以多线程工作为基础进行工作的(当然jmeter没有使用线程池,这里举出来只是为了说明多线程编程在我们工作中的重要地位)。对于如此重要的东西,我们是应该要详细且准确的了解它的工作机制的,下面我们先对组成线程池的各个部件做初步的介绍。
既然是初步介绍,我们在这里就不会详细的说明各个组件的代码,二十把详细的介绍放在线程池工作流程介绍的章节中,这样做的目的是使大家在开头以低廉的阅读成本去大致了解以下线程池各个组件,留下初步的印象,这样即使不去看后面比较难的内容也能对线程池的各个部分有一定的了解,也算没白看。

1、java.util.concurrent.ThreadPoolExecutor

网络上有很多介绍自动创建线程池工具类的文章,所以我们在这里并不打算介绍那些工具,而是直接介绍手动创建线程池使用的工具类,它也是自动创建需要调用的底层类。从类名我们可以看出,这个东西是线程池的执行者。在线程池的工作过程中,它负责协调任务队列、线程工厂和工作者(这些东西会在下面逐一介绍)等组件的工作,在整个线程池的工作过程中处于中心领导地位,是最重要的组件。
因为本文是主要介绍线程池的工作流程的,所以对于出了线程池本身和它的内部类Worker(后面会说)都不会做全面的介绍,只是在用到他们的代码的时候才介绍部分代码,但是这并不影响我们了解线程池的工作流程,至于其他的组件,有兴趣自己看看也挺好的。

2、等待队列

等待队列是线程池缓冲机制的主要组成部分,常用的队列主要有无界队列LinkedBlockingQueue,同步转移队列SynchronousQueue和有界队列ArrayBlockingQueue。他们三个功能基本相同,最大的区别就是无界队列如果在实例化的时候不指定队列长度,则队列长度为int的最大值,所以我们虽然叫它无界队列,但是这个队列实际上还是有界的,只是界限非常广阔;与无界队列相对的就是有界队列,它与无界队列在实例化上的主要区别就是有界队列在实例化的时候必须传入队列长度,正因为这样才叫有界队列;最后是同步转移队列,它的队列长度固定为1,也就是说有任务就给线程池执行,自己是不缓存的。
虽然线程池的等待队列有多种类型,但是我们并不都进行详细的介绍,在下面对线程池工作方式进行解析的内容里,我们只对有界队列做详细介绍。不过大家不要担心,这三个队列基本功能一样,所以实现方式也没有太大区别,只要学会一个就能举一反三。

3、线程工厂

线程工厂类需要实现ThreadFactory接口,这个接口非常简单,只有一个newThread方法,功能更简单,就是在newThread方法被调用后返回一个Thread类型的对象,用于(这个东西下面会介绍)开启线程。在很多时候这都是一个很简单的类,所以我们在这里一口气把它的所有东西说完了,下面我们看一下tomcat实现的线程工厂类:

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.tomcat.util.threads;

import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.tomcat.util.security.PrivilegedSetTccl;

/**
 * Simple task thread factory to use to create threads for an executor
 * implementation.
 */
public class TaskThreadFactory implements ThreadFactory {

    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;
    private final boolean daemon;
    private final int threadPriority;

    public TaskThreadFactory(String namePrefix, boolean daemon, int priority) {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
        this.namePrefix = namePrefix;
        this.daemon = daemon;
        this.threadPriority = priority;
    }

    @Override
    public Thread newThread(Runnable r) {
        TaskThread t = new TaskThread(group, r, namePrefix + threadNumber.getAndIncrement());
        t.setDaemon(daemon);
        t.setPriority(threadPriority);

        // Set the context class loader of newly created threads to be the class
        // loader that loaded this factory. This avoids retaining references to
        // web application class loaders and similar.
        if (Constants.IS_SECURITY_ENABLED) {
            PrivilegedAction<Void> pa = new PrivilegedSetTccl(
                    t, getClass().getClassLoader());
            AccessController.doPrivileged(pa);
        } else {
            t.setContextClassLoader(getClass().getClassLoader());
        }

        return t;
    }
}

大家可以看到,真的非常简单。

4、拒绝策略

拒绝策略是一个非常容易被忽略的东西,因为它是在线程池队列已满且没有多余线程处理任务的情况下才工作的,ThreadPoolExecutor提供CallerRunsPolicy、AbortPloicy、DiscardPolicy和DiscardOldestPolicy四种拒绝测略,他们都实现了RejectedExecutionHandler接口,他们的源码都在ThreadPoolExecutor类的最下面(2000多行的位置)。由于他们实在过于简单,我们这里不粘贴源码,只稍微说一下他们的作用就不再介绍他们。
CallerRunsPolicy被触发的时候直接调用任务对象的run方法来执行任务,这也是一个挺不拒绝的拒绝测略,只要线程池还没有关闭,就直接执行任务的run方法。
AbortPloicy直接丢弃被拒绝的任务,被丢弃后会在控制台打印一条日志,告诉开发人员有任务被丢弃了。
DiscardPolicy直接丢弃被拒绝的任务并且什么也不说。
DiscardOldestPolicy拒绝测略做的事情稍微复杂一些,如果线程池还没有关闭,那么它会从队列里踢掉最老的一个任务,然后把这个任务加进去运行,也是一个挺不拒绝的拒绝测略。
从上面的介绍我们可以看出,AbortPloicy和DiscardPolicy拒绝测略都是直接丢弃任务,这种测略对程序没有什么风险,但是会对数据完整性有影响,也会直接的影响业务。而其他两个决绝测略会执行任务,但是这种执行是有风险的,特别是CallerRunsPolicy拒绝测略,它直接在线程池主线程中执行任务,风险是很大的。
最后,ThreadPoolExecutor的默认拒绝测略defaultHandler是AbortPloicy,就是丢弃加打日志。

5、Worker

Worker这个东西十分重要,他是线程池当中执行任务的工人,它是线程工厂开启新线程的入参传入的任务最终在Worker的run方法里执行。由于在某些时候线程池可能会通过中断的方式停止线程,所以Worker继承了AbstractQueuedSynchronizer类来保护线程在运行任务的时候不被中断。
虽然Worker很重要,但是在这里我们只用这简单的两句话解释它的作用和主要特点,在下面说明线程池工作流程的时候,会详细说明涉及到的Worker代码。

二、线程池的工作流程

线程池的工作流程其实很短。在初始化后就可以通过线程池的execute方法执行任务了,在用execute方法提交任务以后,线程池会先检查当前的工作线程数量是不是比核心线程书香参数规定的小,如果比规定的核心线程数小,那么线程池会直接开启一个新线程来处理请求,处理玩请求后此线程将会常驻在线程池中作为核心线程来等待新的任务分配;如果当前工作的线程数大于或等于规定的核心线程数,线程池会将提交上来的任务放进等待队列中,如果提交成功,会判断线程池的运行状态,如果线程池关闭,就会从队列中移除任务并拒绝任务,如果线程池不关闭,就判断Worker数量,如果符合要求,就增加一个Worker来处理请求;如果以上判断都没成功,就尝试增加一个Worker来处理请求,如果增加失败,就拒绝任务。
以上就是execute方法的大致逻辑,这个方法虽然代码很少,但是逻辑是很复杂的,有条件的可以去详细看。

1、线程池的初始化流程

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

这是线程池的一个构造方法,其他三个构造方法最终都会在补全一些缺省参数后调用这个构造方法,非常简单。

2、execute方法流程

和大家前面看到的简单流程不同,即使我们在本章开头已经详细说明了本方法的流程,但是在阅读这部分代码的时候也很不容易(至少我是这么人为的),在用户线程执行线程池对象的execute方法后,用户线程会立刻返回,任务就交给了线程池的线程去执行,别看这一部分特别短小,但是线程池的所有执行流程都在这个里,我们分析线程池也主要是分析这个方法,可以说,execute方法的流程其实就是线程池的流程。那么首先我们先看它的代码和注释:

/**
     * Executes the given task sometime in the future.  The task
     * may execute in a new thread or in an existing pooled thread.
     *
     * If the task cannot be submitted for execution, either because this
     * executor has been shutdown or because its capacity has been reached,
     * the task is handled by the current {@link RejectedExecutionHandler}.
     *
     * @param command the task to execute
     * @throws RejectedExecutionException at discretion of
     *         {@code RejectedExecutionHandler}, if the task
     *         cannot be accepted for execution
     * @throws NullPointerException if {@code command} is null
     */
    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        /*
         * Proceed in 3 steps:
         *
         * 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.
         */
        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)execute的第一部分

第一部分是第一个if代码块,它对应注释种的第1部,如果当前线程数少于核心线程数,那么增加一个线程来执行任务,增加线程并执行任务的操作就在addWorker方法种完成,workerCountOf©使用来获取当前工人数(也就是线程数)的,先按下不表,我们先看addWorker方法代码:

887     private boolean addWorker(Runnable firstTask, boolean core) {
 888         retry:
 889         for (int c = ctl.get();;) {
 890             // Check if queue empty only if necessary.
 891             if (runStateAtLeast(c, SHUTDOWN)
 892                 && (runStateAtLeast(c, STOP)
 893                     || firstTask != null
 894                     || workQueue.isEmpty()))
 895                 return false;
 896
 897             for (;;) {
 898                 if (workerCountOf(c)
 899                     >= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
 900                     return false;
 901                 if (compareAndIncrementWorkerCount(c))
 902                     break retry;
 903                 c = ctl.get();  // Re-read ctl
 904                 if (runStateAtLeast(c, SHUTDOWN))
 905                     continue retry;
 906                 // else CAS failed due to workerCount change; retry inner loop
 907             }
 908         }
 909
 910         boolean workerStarted = false;
 911         boolean workerAdded = false;
 912         Worker w = null;
 913         try {
 914             w = new Worker(firstTask);
 915             final Thread t = w.thread;
 916             if (t != null) {
 917                 final ReentrantLock mainLock = this.mainLock;
 918                 mainLock.lock();
 919                 try {
 920                     // Recheck while holding lock.
 921                     // Back out on ThreadFactory failure or if
 922                     // shut down before lock acquired.
 923                     int c = ctl.get();
 924
 925                     if (isRunning(c) ||
 926                         (runStateLessThan(c, STOP) && firstTask == null)) {
 927                         if (t.getState() != Thread.State.NEW)
 928                             throw new IllegalThreadStateException();
 929                         workers.add(w);
 930                         workerAdded = true;
 931                         int s = workers.size();
 932                         if (s > largestPoolSize)
 933                             largestPoolSize = s;
 934                     }
 935                 } finally {
 936                     mainLock.unlock();
 937                 }
 938                 if (workerAdded) {
 939                     t.start();
 940                     workerStarted = true;
 941                 }
 942             }
 943         } finally {
 944             if (! workerStarted)
 945                 addWorkerFailed(w);
 946         }
 947         return workerStarted;
 948     }

这个方法代码要分两部分看,第一部分是第887行到908行,这一部分的主要目的是对线程池状态进行校验,第二部分是909行到948行,这一部分是开启新线程的。对于第一部分,逻辑主要集中在898至900这三行中,方法的第二个参数core也在这三行里被用到,他们的含义是,如果core是true,线程池中的当前的工人的数量(也就是线程数)和参数规定的核心线程数进行比较,如果比核心线程数小,就不做什么,如果比核心线程数大,就返回false,下面就不执行了,如果core是false,当前工人数(也就是线程数)就和最大线程数比较。
经过了第一部分的状态校验,我们来到了方法的下一部分,在这里的try块中的914行初始化了一个Worker类的实例,并传入了一个参数firstTask,这个task就是我们传进来的由用户代码使用execute方法传进来的Runnable实例,在Worker的构造方法中,我们先来看这个构造方法:

 618         /**
 619          * Creates with given first task and thread from ThreadFactory.
 620          * @param firstTask the first task (null if none)
 621          */
 622         Worker(Runnable firstTask) {
 623             setState(-1); // inhibit interrupts until runWorker
 624             this.firstTask = firstTask;
 625             this.thread = getThreadFactory().newThread(this);
 626         }

这个构造方法虽然代码不多,但是很重要,甚至注释都很重,蓝字注释说明了这个方法干了什么,绿色注释说明在执行完这个方法以后,本线程是不可中断的,这样做的目的是为了避免线程池在需要清理多余工人和线程的时候错误的将还在初始化执行任务的线程清理了,这个功能是通过Worker类继承的AbstractQueuedSynchronizer类来实现的,具体怎么实现的大家可以百度,它也是ReentrantLock的父类。firstTask就是由execute方法提交上来的任务,就是一个Runnable对象,thread是调用线程工厂后,返回的Thread对象,主意newThread的参数是this,这意味着Worker是直接作为线程的工作对象的,我们将在Worker的run方法中执行firstTask对象的run方法。
我们现在把视线再次拉回线程池的addWorker方法,在初始化了Worker对象且在916行判断了从Worker中取出的线程对象非空后,线程开始获取线程锁,这里要说明以下这个获取锁的线程是什么线程,大家一定要注意,这个获取锁的线程是调用execute方法提交任务的工作线程,也就是用户线程。在生产中,可能会有多个用户线程在同一时间提交任务,但是线程池就一个,为了不乱套,所以这里给加了锁来保证线程同步,锁会在下面的try块的finally代码块中解锁。
在完成加锁进入同步代码块后,主要干的事就是在929行到933行这四行代码里,我们可以看到,这里只是将上面新初始化的Worker对象添加进workers这个集合中,再判断集合中元素的数量是否比现在的largestPoolSize变量中的数值大,如果是就将largestPoolSize的至改成workers集合的元素数量。做完这些工作以后,工作线程在936行离开同步代码块,在939行开始新的执行线程后,用户的工作线程就返回了,任务的执行就交给了线程池的工作线程,并由上面初始化的Woker对象执行任务,addWorker方法在执行成功以后,返回true,失败返回false。到这里我们再将目光拉回execute方法的第一部分,在addWorker方法返回后,如果返回true,就皆大欢喜,任务已经成功提交并执行了,execute方法就直接返回;如果返回false,证明执行失败了,线程池继续做下面的判断,下面的判断我们会在2)中说明。
在这里,我们用大量的篇幅去说明了addWorker方法,以后如果我们又遇到了这个方法,我们将不在仔细说明他,到时候请大家能自己翻回来看看。

2)execute的第二部分

第二部分就是第二个if代码块的if部分,这部分我们就要请出线程池的另一个主角了,暨等待队列。等待队列对于线程池又重大意义,它不仅是线程池的缓存组件,也是线程池多线程同步的重要组成部分。而这一部分我认为也是线程池最难的部分(就这么几行代码,但是里面包含的逻辑很复杂),我们的视线老来回在等待队列,线程池和Worker中间来回切换。
为了说明这部分内容,我们先把目光放到上节的addWorker方法中,在939行执行开始新线程后,当线程执行完了,Worker的run方法在干什么呢,为了解答这个问题,我们先看Worker的run方法:

 628         /** Delegates main run loop to outer runWorker. */
 629         public void run() {
 630             runWorker(this);
 631         }

太简单了,但是要注意,runWorker这个方法是在线程池代码里的,而Worker作为线程池ThreadPoolExecuteor的内部类,调用外面的runWorker方法,我们先来看这个runWorker:

1109     final void runWorker(Worker w) {
1110         Thread wt = Thread.currentThread();
1111         Runnable task = w.firstTask;
1112         w.firstTask = null;
1113         w.unlock(); // allow interrupts
1114         boolean completedAbruptly = true;
1115         try {
1116             while (task != null || (task = getTask()) != null) {
1117                 w.lock();
1118                 // If pool is stopping, ensure thread is interrupted;
1119                 // if not, ensure thread is not interrupted.  This
1120                 // requires a recheck in second case to deal with
1121                 // shutdownNow race while clearing interrupt
1122                 if ((runStateAtLeast(ctl.get(), STOP) ||
1123                      (Thread.interrupted() &&
1124                       runStateAtLeast(ctl.get(), STOP))) &&
1125                     !wt.isInterrupted())
1126                     wt.interrupt();
1127                 try {
1128                     beforeExecute(wt, task);
1129                     try {
1130                         task.run();
1131                         afterExecute(task, null);
1132                     } catch (Throwable ex) {
1133                         afterExecute(task, ex);
1134                         throw ex;
1135                     }
1136                 } finally {
1137                     task = null;
1138                     w.completedTasks++;
1139                     w.unlock();
1140                 }
1141             }
1142             completedAbruptly = false;
1143         } finally {
1144             processWorkerExit(w, completedAbruptly);
1145         }
1146     }

这个方法的注释同样重要,但是这里不截图了。我们先看这个方法,从1110行到1112行这三行很好理解,工作线程(这里就已经是线程池的工作线程了)获取当前线程对象,去除Worker对象的firstTask属性并将其置空。而紧随其后的1113行的解锁操作我们和1117的加锁操作放一起说,这两行的意思是如果线程运行在这两行意见的代码中,那么这个线程是可中断的,这个可中断的状态会被线程池获取,在需要停止工作线程的时候会判断这个线程是不是可被中断,如果能被中断,就中断它,这个功能是Worker的父类实现的,有兴趣的可以去看以下。
在进入try块后,如果task不为空,就直接在1130行执行task任务对象的run方法(这里请大家不要疑惑,task明明是Runnable对象,但是这里Runnable只是规定对象类型,并不是要开启线程的意思,以为这已经是一个新的工作线程了,所以这里就直接调用了run方法)但是这一步已经在1)中走过了,这里我们分析,在执行完task.run()后,再次回到这个while这里的时候,这个getTask方法,干了什么。
我们先来看这个getTask方法:

1028     private Runnable getTask() {
1029         boolean timedOut = false; // Did the last poll() time out?
1030
1031         for (;;) {
1032             int c = ctl.get();
1033
1034             // Check if queue empty only if necessary.
1035             if (runStateAtLeast(c, SHUTDOWN)
1036                 && (runStateAtLeast(c, STOP) || workQueue.isEmpty())) {
1037                 decrementWorkerCount();
1038                 return null;
1039             }
1040
1041             int wc = workerCountOf(c);
1042
1043             // Are workers subject to culling?
1044             boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
1045
1046             if ((wc > maximumPoolSize || (timed && timedOut))
1047                 && (wc > 1 || workQueue.isEmpty())) {
1048                 if (compareAndDecrementWorkerCount(c))
1049                     return null;
1050                 continue;
1051             }
1052
1053             try {
1054                 Runnable r = timed ?
1055                     workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
1056                     workQueue.take();
1057                 if (r != null)
1058                     return r;
1059                 timedOut = true;
1060             } catch (InterruptedException retry) {
1061                 timedOut = false;
1062             }
1063         }
1064     }

这个方法同样不长,但是和前面逻辑很复杂的方法不同,这个方法逻辑很简单,而且在关键的地方jdk也给出了注释,在看这个方法的时候,我们只需要关注最后一个try块里的内容就可以了,暨1054行至1058行的内容。但是,我们还是先要解释一下1031行for循环的意义,这个for循环是要和1029行的boolean参数合起来看,这个参数会在1056行等待队列返回为null时在1059行被置成false,然后在第二个循环时使1046行的if判断条件判成true,最后会导致本方法在1049行返回一个null,使runWorker方法跳转到最后的finally方法使线程退出,但是这部分内容我们会在讲线程退出时讨论
在这三几行代码中,1054行至1056行是一个三目运算符,很明显在这里timed是false,所以我们走了1056行代码。这里要说一下,在这之前,我们介绍了几种等待队列,但是没有说具体我们用的哪个,在这里我们规定以下,我们在线程池初始化的时候给线程池的等待队列是无界队列,暨LinkedBlockingQueue的对象,那么下面我们就接上面,看下它的take方法:

 427     public E take() throws InterruptedException {
 428         final E x;
 429         final int c;
 430         final AtomicInteger count = this.count;
 431         final ReentrantLock takeLock = this.takeLock;
 432         takeLock.lockInterruptibly();
 433         try {
 434             while (count.get() == 0) {
 435                 notEmpty.await();
 436             }
 437             x = dequeue();
 438             c = count.getAndDecrement();
 439             if (c > 1)
 440                 notEmpty.signal();
 441         } finally {
 442             takeLock.unlock();
 443         }
 444         if (c == capacity)
 445             signalNotFull();
 446         return x;
 447     }

这个方法逻辑非常简单,就是当线程访问这个方法的时候如果这个队列里是空的,那么就在435行处等待,直到又人唤醒这个线程,什么时候唤醒呢,当这个队列非空的时候就会唤醒,那么被唤醒的线程在醒来后会再次检查队列的非空状态,如果是非空的,就往下走,最后返回任务对象。如果在取完了以后发现队列里还是非空的,那么就唤醒另一个在435行代码处等待的线程,返回一个任务对象。另外再看这个方法的时候一定要注意,由于432行给的是可中断的等待状态,所以这里等待的线程是可以被线程池中断掉的。
到这里,我们最后发现,再执行线程池runWorker方法的1116行代码时:

1116             while (task != null || (task = getTask()) != null) {

最终阻塞在了队列的非空条件的wait方法上,就是上面take方法的435行,当队列非空后就会唤醒线程池的工作线程去执行任务,但是什么时候队列非空呢,我们往下看。
在这里我们终于要进入execute方法第二部分的正式讲解了,上面我们知道了,当核心线程执行完任务后,会阻塞在ThreadPoolExecutor的1116行上,但是实际上阻塞在了等待队列的(专指这次我们用的队列)432行上,等待队列非空。这时,我们假设现在所有的核心线程都执行过任务了,都已经在阻塞在了等待队列的432行的时候,这时工作线程又提交了一个任务,那么execute方法的第一个if代码块的判断条件就是否,我们就直接进入到了本节所讲的第二部分,代码如下:

1349         if (isRunning(c) && workQueue.offer(command)) {
1350             int recheck = ctl.get();
1351             if (! isRunning(recheck) && remove(command))
1352                 reject(command);
1353             else if (workerCountOf(recheck) == 0)
1354                 addWorker(null, false);
1355         }

这里1349行的isRunning方法判断的在正常状态一直时真,所以我们直接看后面,这里工作线程通过等待队列的offer方法向等待队列里放了一个任务对象,这时机灵的哥们儿应该反应过来了,这样队列就非空了,就可以唤醒核心线程去干活了,如果你这么想,那恭喜你答对了。
就如同上面所说的等待队列的offer方法就俩作用,一是往队列里放一个任务对象,然后就是唤醒一个工作线程去干活,我们现在看它的代码:

 403     public boolean offer(E e) {
 404         if (e == null) throw new NullPointerException();
 405         final AtomicInteger count = this.count;
 406         if (count.get() == capacity)
 407             return false;
 408         final int c;
 409         final Node<E> node = new Node<E>(e);
 410         final ReentrantLock putLock = this.putLock;
 411         putLock.lock();
 412         try {
 413             if (count.get() == capacity)
 414                 return false;
 415             enqueue(node);
 416             c = count.getAndIncrement();
 417             if (c + 1 < capacity)
 418                 notFull.signal();
 419         } finally {
 420             putLock.unlock();
 421         }
 422         if (c == 0)
 423             signalNotEmpty();
 424         return true;
 425     }

同样很简单,几句话就能说明白。首先获得当前队列中的元素个数,将传入的对象包装成队列元素对象(我也不知道这么说和不合适,但是我找不到合适的说法了,有兴趣的可以去看队列代码,这里又详细的解释为什么这么做),之后把线程锁住,在同步代码块里先判断队列是否满了,满了就直接返回否,没满就将对象放进队列里(由415行那个方法完成)。417和418两行代码是告诉后续工作线程可以继续提交任务,线程还没满的。最后在try块的finally方法里解锁,423行的代码和418行的代码作用一样,最后返回true。
我们线程池的第二部分的第一行代码终于执行完了,我们成功的把对象添加进了等待队列,后面的判断就很简单了,我们再回顾一下第二部分代码:

1349         if (isRunning(c) && workQueue.offer(command)) {
1350             int recheck = ctl.get();
1351             if (! isRunning(recheck) && remove(command))
1352                 reject(command);
1353             else if (workerCountOf(recheck) == 0)
1354                 addWorker(null, false);
1355         }

我们可以看到,再1351行的代码上的判断由于前半部分在正常情况下也都是false,所以后半部分和1352行拒绝方法都是不会执行的,1353行和1354行的意思是如果当前线程池中没有工作线程,就新增一个工作线程。

3)execute的第三部分

第三部分代码非常简单,而且涉及的方法前面都讲过了,所以我们直接说它的逻辑,如果代码走到这里,说明当前线程数大于或等于规定的核心线程数且队列满了,这种情况下线程池会直接增加一个线程来执行任务,但是增加线程也不是无线的,我们看下面的代码:

1356         else if (!addWorker(command, false))
1357             reject(command);

但是即使这么简单的流程这么简单的代码也有一点玄机,就是addWorker方法给的第二个布尔类型的参数给的是false,第一部分给的是true,给false表示新建线程不超过最大线程数,具体逻辑请参阅第一部分的解析。
到这里,整个线程池的工作流程就讲完了。

3、线程的退出

线程池的一大特点的就是当当前工作线程数大于核心线程数时,线程池会退出已经执行了任务的空闲非核心线程,让线程池中的工作线程保持在我们规定的核心线程数量上。但是线程池是怎样退出线程的,还要从我们上节讲过的runWorker方法的最后一个finally代码块说起,涉及的代码如下:

1143         } finally {
1144             processWorkerExit(w, completedAbruptly);
1145         }

但是什么时候会跳到这里呢,我们回顾上面就可以看出,当runWorker方法的1116行while条件里的getTask返回null的时候,while循环跳出,这个方法结束的时候,才会走到上图的finally代码块,那么什么时候getTask方法返回null呢,我们得先看getTask的部分代码:

1043             // Are workers subject to culling?
1044             boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
1045
1046             if ((wc > maximumPoolSize || (timed && timedOut))
1047                 && (wc > 1 || workQueue.isEmpty())) {
1048                 if (compareAndDecrementWorkerCount(c))
1049                     return null;
1050                 continue;
1051             }

这里解释一下(因为线程池的退出较其他部分不是十分重要,所以这里不做详细分析,只在这里解释)这段代码,首先allCoreThreadTimeOut,先看他的注释:

526     /**
 527      * If false (default), core threads stay alive even when idle.
 528      * If true, core threads use keepAliveTime to time out waiting
 529      * for work.
 530      */
 531     private volatile boolean allowCoreThreadTimeOut;

通过注释我们也很同意明白它的含义,这里通常创建的线程池都是取它的默认值的,就是false。1044行短路或后面的条件就是当前线程数是否大于核心线程数,那这里由于我们讨论的是线程池在当前线程数大于核心线程数的时候的线程退出行为,所以这里是true,所以timed就是true。到1046行四个判断条件通过我们设定的条件可以看到,由于现在本非核心线程是空闲线程,所以队列里是没有任务的,所以,这四个条件的判断结果就是,(wc > maximumPoolSize || (timed && timedOut))是true,注意这个timedOut,在队列没有任务的时候,从1031行开始的for循环会在第一次循环时在1059行将timeOut参数置成true,那么在第二次循环到来的时候timedOut就是true,这个逻辑在本大第2节的第2)小节讲过,而后面(wc > 1 || workQueue.isEmpty())这半部分也是true的,那么整个判断条件就为true,那么整个方法也就进入了1048行的if判断条件,在这里执行了compareAndDecrementWorkerCount方法,我们先来看这个方法:

 420     /**
 421      * Attempts to CAS-decrement the workerCount field of ctl.
 422      */
 423     private boolean compareAndDecrementWorkerCount(int expect) {
 424         return ctl.compareAndSet(expect, expect - 1);
 425     }

这里执行的是AtomicInteger对象ctl的compareAndSet方法,这里我们通过AtomicInteger的特性就可以知道,这个方法就是通过CAS来设置值的,设置成功后就回返回true,在当前条件下,这个设置是一定会返回true的,这里,对这个方法的解释就仅限于此,对AtomicInteger感兴趣的可以自行去学习,这样,gatTask方法就在线程空闲的时候在第二个循环,在1049行返回null了,是的调用它的runWorker方法跳出while循环,在1144行执行退出方法processWorkerExit,在看这个方法的时候注意completedAbruptly这个boolean参数,它会在runWorker跳出while循环是在1142行被置成false,所以它在作为processWorkerExit方法的入参的时候的值是false,那么我们先看这个方法:

983     private void processWorkerExit(Worker w, boolean completedAbruptly) {
 984         if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
 985             decrementWorkerCount();
 986
 987         final ReentrantLock mainLock = this.mainLock;
 988         mainLock.lock();
 989         try {
 990             completedTaskCount += w.completedTasks;
 991             workers.remove(w);
 992         } finally {
 993             mainLock.unlock();
 994         }
 995
 996         tryTerminate();
 997
 998         int c = ctl.get();
 999         if (runStateLessThan(c, STOP)) {
1000             if (!completedAbruptly) {
1001                 int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
1002                 if (min == 0 && ! workQueue.isEmpty())
1003                     min = 1;
1004                 if (workerCountOf(c) >= min)
1005                     return; // replacement not needed
1006             }
1007             addWorker(null, false);
1008         }
1009     }

最终可以看到,线程在996行执行tryTerminate方法,这个方法里的逻辑都很简单,点进去看会返现这个方法在线程池正常的时候啥也不会干,不正常的情况就是给可中断的线程发送中断信号。所以线程会进入999行的判断,这个判断在正常情况下也是true,所以进入1000行的if代码块,这里由于completedAbruptly是传入的false参数,所以if的判断条件为true,又因为在1004行正常情况下都会判断成true,所以本线程在1005行就因为本方法的结束而结束了,这就是非核心线程的退出逻辑,当然由于篇幅所限,这部分讲的略有潦草,有兴趣的可以点开疑惑的地方自己看一下。
本节主要讲解了非核心线程池的退出,线程池其他的退出机制本文就不再讲解了。

三、总结

我们知道,多线程是提升性能的最常用的手段,在一般的项目中,多线程最后落地基本上也是通过线程池来实现的。线程池通过协调各个组件,讲多个线程组织起来,除了通过在等待队列中使用锁的条件等待来代替线程自旋获取任务外,有使用可重入锁在特定的代码处保证线程同步,又使用AQS保证了线程只会在合适的时候响应中断,可以说涉及的非常合理和高效。
但是在多线程的时候一定要知道什么条件下是可以使用多线程的,其中一个重要条件就是工作线程的处理逻辑非常简单,如果我们的单个工作线程就耗尽了所有的cpu资源,那么多线程也是没有意义的。但是这里并不是在说多线程或者是线程池不好,而是说明线程池本身作为一个专门为多线程设计的工具,是有它专门的应用场景的,并不是万用的灵药,很多场景也不是一用线程池就会提高性能,所以在什么时候使用,以什么方式用就是一个很考验人的问题了,并且值得项目人员特别是性能测试人员思考。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值