ThreadPoolExecutor 参数详解

 

 

一、 ThreadPoolExecutor 数据成员

 private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
 
  • 1

ctl 主要用于存储线程池的工作状态以及池中正在运行的线程数。显然要在一个整型变量存储两个数据,只能将其一分为二。其中高3bit用于存储线程池的状态,低位的29bit用于存储正在运行的线程数。

1.1、线程池的状态

线程池具有以下五种状态,当创建一个线程池时初始化状态为 RUNNING 。

线程池 的状态说明
RUNNING 允许提交并处理任务
SHUTDOWN 不允许提交新的任务,但是会处理完已提交的任务
STOP 不允许提交新的任务,也不会处理阻塞队列中未执行的任务,
并设置正在执行的线程的中断标志位
TIDYING 所有任务执行完毕,池中工作的线程数为0,等待执行terminated()勾子方法
TERMINATED terminated()勾子方法执行完毕

注意,这里说的是 线程池 的状态,而不是 池中 线程的状态。

调用线程池的 shutdown 方法,将线程池由 RUNNING(运行状态)转换为 SHUTDOWN状态。

调用线程池的 shutdownNow 方法,将线程池由 RUNNING 或 SHUTDOWN 状态转换为 STOP 状态。

SHUTDOWN 状态 和 STOP 状态 先会转变为 TIDYING 状态,最终都会变为 TERMINATED

// 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; }
 
  • 1
  • 2
  • 3
  • 4

ThreadPoolExecutor 同时提供上述三个方法用于池中的线程查看线程池的状态和计算正在运行的线程数。

1.2、ThreadPoolExecutor 的核心参数

private int largestPoolSize;
private final BlockingQueue<Runnable>workQueue;
private volatile long keepAliveTime;
private volatile int corePoolSize;
private volatile int maximumPoolSize;
private volatile ThreadFactory threadFactory;
private volatile RejectedExecutionHandler handler;
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

上述数据成员对线程池的性能也有很大的影响,我会将它们放到构造中讲解。

private final HashSet<Worker> workers= new HashSet<Worker>();
private long completedTaskCount;
private volatile boolean allowCoreThreadTimeOut;
private int largestPoolSize;
 
  • 1
  • 2
  • 3
  • 4

completedTaskCount 表示线程池已完成的任务数。
allowCoreThreadTimeeOut 表示是否允许核心线程在空闲状态下自行销毁。
largestPoolSize 表示线程池从创建到现在,池中线程的最大数量

1.3、workers

private final HashSet<Worker> workers = new HashSet<Worker>();
 
  • 1

workers 是个HashSet容器,它存储的是Worker类的对象,Worker是线程池的内部类,它继承了Runnable接口,不严格的情况下,可以将一个Worker对象看成Thread对象,也就是工作的线程。shutdown和shutdownNow方法中会使用workers完成对所有线程的遍历。

1.4、mainLock

private final ReentrantLock mainLock =new ReentrantLock();
private final Condition termination = mainLock.newCondition();
 
  • 1
  • 2

mainLock 主要用于同步访问(或者说改变)线程池的状态以及线程池的各项参数,比如 completedTaskCountworkers 等。

awaitTermination 方法中,(mianLock的)termination是用于延时的条件队列。

二、 ThreadPoolExecutor 的构造函数

public  ThreadPoolExecutor(intcorePoolSize,
        int maximumPoolSize,
        long keepAliveTime,
        TimeUnit unit,
        BlockingQueue<Runnable> workQueue,
        ThreadFactory threadFactory,
        RejectedExecutionHandler handler)
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

线程池的构造函数参数多达7个,现在我们一一来分析它们对线程池的影响。

  1. corePoolSize :线程池中核心线程数的最大值
  2. maximumPoolSize :线程池中能拥有最多线程数
  3. workQueue :用于缓存任务的阻塞队列

2.1、corePoolSize、maximumPoolSize、workQueue 三者关系

我们现在通过向线程池添加新的任务来说明着三者之间的关系。

(1)如果没有空闲的线程执行该任务,且当前运行的线程数少于 corePoolSize ,则添加新的线程执行该任务。

(2)如果没有空闲的线程执行该任务,且当前的线程数等于 corePoolSize ,同时阻塞队列未满,则将任务入队列,而不添加新的线程。

(3)如果没有空闲的线程执行该任务,且阻塞队列已满,同时池中的线程数小于maximumPoolSize ,则创建新的线程执行任务。

(4)如果没有空闲的线程执行该任务,且阻塞队列已满,同时池中的线程数等于maximumPoolSize ,则根据构造函数中的 handler 指定的策略来拒绝新的任务。

注意,线程池并没有标记哪个线程是核心线程,哪个是非核心线程,线程池只关心核心线程的数量。

通俗解释, 如果把线程池比作一个单位的话,corePoolSize 就表示正式工,线程就可以表示一个员工。
当我们向单位委派一项工作时,如果单位发现正式工还没招满,单位就会招个正式工来完成这项工作。随着我们向这个单位委派的工作增多,即使正式工全部满了,工作还是做不完,那么单位只能按照我们新委派的工作按先后顺序将它们找个地方搁置起来,这个地方就是 workQueue ,等正式工完成了手上的工作,就到这里来取新的任务。

如果不巧,年末了,各个部门都向这个单位委派任务,导致 workQueue 已经没有空位置放新的任务,于是单位决定招点临时工吧(临时工:又是我!)。临时工也不是想招多少就找多少,上级部门通过这个单位的 maximumPoolSize 确定了你这个单位的人数的最大值,换句话说最多招maximumPoolSize – corePoolSize 个临时工。当然,在线程池中,谁是正式工,谁是临时工是没有区别,完全同工同酬。

参考文章:
corePoolSize、maximumPoolSize、workQueue 三者关系的文章:
https://www.cnblogs.com/cdf-opensource-007/p/8769777.html

2.2、keepAliveTime、TimeUnit 存活时间和单位

keepAliveTime :表示空闲线程的存活时间。
TimeUnit unit :表示keepAliveTime的单位。

为了解释 keepAliveTime 的作用,我们在上述情况下做一种假设。假设线程池这个单位已经招了些临时工,但新任务没有继续增加,所以随着每个员工忙完手头的工作,都来workQueue领取新的任务(看看这个单位的员工多自觉啊)。随着各个员工齐心协力,任务越来越少,员工数没变,那么就必定有闲着没事干的员工。这样的话领导不乐意啦,但是又不能轻易fire没事干的员工,因为随时可能有新任务来,于是领导想了个办法,设定了 keepAliveTime,当空闲的员工在 keepAliveTime 这段时间还没有找到事情干,就被辞退啦,毕竟地主家也没有余粮啊!当然辞退到 corePoolSize 个员工时就不再辞退了,领导也不想当光杆司令啊!

2.3、workQueue 任务队列

workQueue :它决定了缓存任务的排队策略。对于不同的应用场景我们可能会采取不同的排队策略,这就需要不同类型的队列。这个队列需要一个实现了BlockingQueue接口的任务等待队列。

在ThreadPoolExecutor线程池的API文档中,一共推荐了三种等待队列,它们是:SynchronousQueueLinkedBlockingQueueArrayBlockingQueue

可参考:https://blog.csdn.net/xiaojin21cen/article/details/87363143

2.3.2、有限队列

  • ArrayBlockingQueue:(基于数据结构的有界的阻塞队列)
    基于数组结构的有界阻塞队列。按 FIFO(先进先出)原则对元素进行操作。

    尾部插入元素,头部取出元素。这是一个典型的“有界缓存区”,
    数组的大小一旦固定,就不能再增加其容量。
    试图向 已满队列 中放入元素会导致操作受阻塞;试图从 空队列 中提取元素将导致类似阻塞。

  • SynchronousQueue :(不存储元素的阻塞队列)
    不存储元素的阻塞队列
    每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于 阻塞状态
    吞吐量通常要高于LinkedBlockingQueue
    静态工厂方法 Executors.newCachedThreadPool 使用了这个队列。

2.3.3、 无界队列

  • LinkedBlockingQueue:(基于单向链表的无界的阻塞队列,尾部插入元素,头部取出元素)
    可以指定容量,也可以不指定容量。由于它具有“无限容量”的特性,所以我还是将它归入了无限队列的范畴(实际上任何无限容量的队列/栈都是有容量的,这个容量就是 Integer.MAX_VALUE )。
    LinkedBlockingQueue 的实现是基于链表结构,而不是类似 ArrayBlockingQueue 那样的数组。但实际使用过程中,不需要关心它的内部实现,如果指定了LinkedBlockingQueue 的容量大小,那么它反映出来的使用特性就和 ArrayBlockingQueue 类似了。
    LinkedBlockingQueue 的内部结构决定了它只能从队列尾部插入,从队列头部取出元素;

  • PriorityBlockingQueue (一个具有 优先级的无限阻塞队列
    PriorityBlockingQueue 是一个按照优先级进行内部元素排序的无限队列。存放在PriorityBlockingQueue 中的元素必须实现 Comparable 接口,这样才能通过实现compareTo() 方法进行排序。优先级最高的元素将始终排在队列的头部;PriorityBlockingQueue 不会保证优先级一样的元素的排序,也不保证当前队列中除了优先级最高的元素以外的元素,随时处于正确排序的位置。

  • LinkedBlockingDeque
    LinkedBlockingDeque 是一个基于链表的双端队列。与LinkedBlockingQueue不同,是 LinkedBlockingDeque 既可以从尾部插入/取出元素,还可以从头部插入元素/取出元素。

  • LinkedTransferQueue
    LinkedTransferQueue 也是一个无限队列,它除了具有一般队列的操作特性外(先进先出),还具有一个阻塞特性:LinkedTransferQueue可以由一对生产者/消费者线程进行操作,当消费者将一个新的元素插入队列后,消费者线程将会一直等待,直到某一个消费者线程将这个元素取走,反之亦然。

2.4、threadFactory

threadFactory :指定创建线程的工厂

实际上 ThreadPoolExecutor 类中还有很多重载的构造函数,下面这个构造函数在Executors中经常用到。

public ThreadPoolExecutor(int corePoolSize,
        int maximumPoolSize,
        long keepAliveTime,
        TimeUnit unit,
        BlockingQueue<Runnable> workQueue) {

    this ( corePoolSize, maximumPoolSize, 
		keepAliveTime, unit, workQueue,
	 	Executors.defaultThreadFactory(),
 		defaultHandler );
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

注意到上述的构造方法使用 Executors 中的defaultThreadFactory() 线程工厂 和 ThreadPoolExecutor 中的 defaultHandler 抛弃策略。

使用 defaultThreadFactory 创建的线程同属于相同的线程组,具有同为 Thread.NORM_PRIORITY 的优先级,以及名为 “pool-XXX-thread-” 的线程名(XXX为创建线程时顺序序号),且创建的线程都是非守护进程。

2.5、 handler 拒绝策略

handler :表示当 workQueue 已满,且池中的线程数达到 maximumPoolSize 时,线程池拒绝添加新任务时采取的策略。

为了解释 handler 的作用,我们在上述情况下做另一种假设。假设线程池这个单位招满临时工,但新任务依然继续增加,线程池从上到下,从里到外真心忙的不可开交,阻塞队列也满了,只好拒绝上级委派下来的任务。怎么拒绝是门艺术,handler 一般可以采取以下四种取值。

策略备注
ThreadPoolExecutor.AbortPolicy() 抛出RejectedExecutionException异常
ThreadPoolExecutor.CallerRunsPolicy() 由向线程池提交任务的线程来执行该任务
ThreadPoolExecutor.DiscardPolicy() 抛弃当前的任务
ThreadPoolExecutor.DiscardOldestPolicy() 抛弃最旧的任务(最先提交而没有得到执行的任务)

CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy 处理器针对被拒绝的任务并不是一个很好的处理方式。

  • CallerRunsPolicy 在非线程池以外直接调用任务的run方法,可能会造成线程安全上的问题;
  • DiscardPolicy 默默的忽略掉被拒绝任务,也没有输出日志或者提示,开发人员不会知道线程池的处理过程出现了错误;
  • DiscardOldestPolicy 中e.getQueue().poll()的方式好像是科学的,但是如果等待队列出现了容量问题,大多数情况下就是这个线程池的代码出现了BUG。

最科学的的还是 AbortPolicy 提供的处理方式:抛出异常,由开发人员进行处理。

2.6、defaultHandler 默认拒绝策略

defaultHandler 缺省抛弃策略是ThreadPoolExecutor.AbortPolicy()

除了在创建线程池时指定上述参数的值外,还可在线程池创建以后通过如下方法进行设置。

public void allowCoreThreadTimeOut(boolean value)
public void setKeepAliveTime(long time,TimeUnit unit)
public void setMaximumPoolSize(int maximumPoolSize)
public void setCorePoolSize(int corePoolSize)
public void setThreadFactory(ThreadFactory threadFactory)
public void setRejectedExecutionHandler(RejectedExecutionHandler  handler)
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

三、其它有关涉及池中线程数量的相关方法

public void allowCoreThreadTimeOut(boolean value)
public int prestartAllCoreThreads()
 
  • 1
  • 2

默认情况下,当池中有空闲线程,且线程的数量大于 corePoolSize 时,空闲时间超过 keepAliveTime 的线程会自行销毁,池中仅仅会保留 corePoolSize 个线程。如果线程池中调用了 allowCoreThreadTimeOut 这个方法,则空闲时间超过 keepAliveTime 的线程全部都会自行销毁,而不必理会corePoolSize 这个参数。

如果池中的线程数量小于 corePoolSize 时,调用prestartAllCoreThreads 方法,则无论是否有待执行的任务,线程池都会创建新的线程,直到池中线程数量达到 corePoolSize

四、Executors 线程池工具类

为了防止使用者错误搭配 ThreadPoolExecutor 构造函数的各个参数以及更加方便简洁的创建ThreadPoolExecutor对象,JavaSE中又定义了Executors类,Eexcutors类提供了创建常用配置线程池的方法。以下是Executors常用的三个创建线程池的源代码。

从源码中可以看出,Executors 间接的调用了重载的ThreadPoolExecutor 构造函数,并帮助用户根据不同的应用场景,配置不同的参数。

4.1、newCachedThreadPool

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0,Integer.MAX_VALUE, 
    					 60L, TimeUnit.SECONDS,
						 new SynchronousQueue<Runnable>());
}
 
  • 1
  • 2
  • 3
  • 4
  • 5

newCachedThreadPool :使用SynchronousQueue作为阻塞队列,队列无界,线程的空闲时限为60秒。这种类型的线程池非常适用IO密集的服务,因为IO请求具有密集、数量巨大、不持续、服务器端CPU等待IO响应时间长的特点。服务器端为了能提高CPU的使用率就应该为每个IO请求都创建一个线程,以免CPU因为等待IO响应而空闲。

4.2、newFixedThreadPool

public static ExecutorService newFixedThreadPool(int nThreads){
    return new ThreadPoolExecutor(nThreads,nThreads,0L,TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
 
  • 1
  • 2
  • 3
  • 4

newFixedThreadPool:需指定核心线程数,核心线程数和最大线程数相同,使用LinkedBlockingQueue 作为阻塞队列,队列无界,线程空闲时间0秒。这种类型的线程池可以适用CPU密集的工作,在这种工作中CPU忙于计算而很少空闲,由于CPU能真正并发的执行的线程数是一定的(比如四核八线程),所以对于那些需要CPU进行大量计算的线程,创建的线程数超过CPU能够真正并发执行的线程数就没有太大的意义。

4.3、newSingleThreadExecutor

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService (
			new ThreadPoolExecutor(1,1, 0L,TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>() )
	);
}
 
  • 1
  • 2
  • 3
  • 4
  • 5

newSingleThreadExecutor:池中只有一个线程工作,阻塞队列无界,它能保证按照任务提交的顺序来执行任务。

五、任务的提交过程

5.1 submit方法源码

public Future<?> submit(Runnable task) {
    if (task == null) 
		throw new NullPointerException();

    RunnableFuture<Void> ftask = newTaskFor(task, null);
    execute(ftask);
    return ftask;
}
 
public <T> Future<T> submit(Callable<T> task) {
    if (task == null)
		throw new NullPointerException();

    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);
    return ftask;
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

submit 的实现方法位于抽象类 AbstractExecutorService 中,而此时 execute 方法还未实现(而是在 AbstractExecutorService 的继承类 ThreadPoolExecutor 中实现)。submit 有三种重载方法,这里我选取了两个常用的进行分析,可以看出无论哪个submit 方法都最终调用了 execute 方法。

5.2 execute方法源码

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    /*
     * clt记录着runState和workerCount
     */
    int c = ctl.get();
    /*
     * workerCountOf方法取出低29位的值,表示当前活动的线程数;
     * 如果当前活动线程数小于corePoolSize,则新建一个线程放入线程池中;
     * 并把任务添加到该线程中。
     */
    if (workerCountOf(c) < corePoolSize) {
        /*
         * addWorker中的第二个参数表示限制添加线程的数量是根据corePoolSize来判断还是maximumPoolSize来判断;
         * 如果为true,根据corePoolSize来判断;
         * 如果为false,则根据maximumPoolSize来判断
         */
        if (addWorker(command, true))
            return;
        /*
         * 如果添加失败,则重新获取ctl值
         */
        c = ctl.get();
    }
    /*
     * 如果当前线程池是运行状态并且任务添加到队列成功
     */
    if (isRunning(c) && workQueue.offer(command)) {
        // 重新获取ctl值
        int recheck = ctl.get();
        // 再次判断线程池的运行状态,如果不是运行状态,由于之前已经把command添加到workQueue中了,
        // 这时需要移除该command
        // 执行过后通过handler使用拒绝策略对该任务进行处理,整个方法返回
        if (! isRunning(recheck) && remove(command))
            reject(command);
        /*
         * 获取线程池中的有效线程数,如果数量是0,则执行addWorker方法
         * 这里传入的参数表示:
         * 1. 第一个参数为null,表示在线程池中创建一个线程,但不去启动;
         * 2. 第二个参数为false,将线程池的有限线程数量的上限设置为maximumPoolSize,添加线程时根据maximumPoolSize来判断;
         * 如果判断workerCount大于0,则直接返回,在workQueue中新增的command会在将来的某个时刻被执行。
         */
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    /*
     * 如果执行到这里,有两种情况:
     * 1. 线程池已经不是RUNNING状态;
     * 2. 线程池是RUNNING状态,但workerCount >= corePoolSize并且workQueue已满。
     * 这时,再次调用addWorker方法,但第二个参数传入为false,将线程池的有限线程数量的上限设置为maximumPoolSize;
     * 如果失败则拒绝该任务
     */
    else if (!addWorker(command, false))
        reject(command);
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

由于execute方法中多次调用 addWorker ,我们这里就简要介绍一下它,这个方法的主要作用就是创建一个线程来执行Runnnable对象。

addWorker(Runnable firstTask, boolean core)
 
  • 1

第一个参数 firstTask 不为null,则创建的线程就会先执行 firstTask对象,然后去阻塞队列中取任务,否直接到阻塞队列中获取任务来执行。
第二个参数,core 参数为真,则用 corePoolSize 作为池中线程数量的最大值;
为假,则以maximumPoolSize 作为池中线程数量的最大值。

简要分析一下execute源码,执行一个Runnable对象时,首先通过workerCountOf(c) 获取线程池中线程的数量,如果池中的数量小于 corePoolSize 就调用 addWorker 添加一个线程来执行这个任务。否则通过 workQueue.offer(command) 方法入列。如果入列成功还需要在一次判断池中的线程数,因为我们创建线程池时可能要求核心线程数量为0,所以我们必须使用 addWorker(null, false) 来创建一个临时线程去阻塞队列中获取任务来执行。

isRunning( c ) 的作用是判断线程池是否处于运行状态,如果入列后发现线程池已经关闭,则出列。不需要在入列前判断线程池的状态,因为判断一个线程池工作处于RUNNING状态到执行入列操作这段时间,线程池可能被其它线程关闭了,所以提前判断毫无意义。

5.3、addWorker源码

private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (;;) {
        int c = ctl.get();
        // 获取运行状态
        int rs = runStateOf(c);
         
        /*
         * 这个if判断
         * 如果rs >= SHUTDOWN,则表示此时不再接收新任务;
         * 接着判断以下3个条件,只要有1个不满足,则返回false:
         * 1. rs == SHUTDOWN,这时表示关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务
         * 2. firsTask为空
         * 3. 阻塞队列不为空
         *
         * 首先考虑rs == SHUTDOWN的情况
         * 这种情况下不会接受新提交的任务,所以在firstTask不为空的时候会返回false;
         * 然后,如果firstTask为空,并且workQueue也为空,则返回false,
         * 因为队列中已经没有任务了,不需要再添加线程了
         */
        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;
        for (;;) {
            // 获取线程数
            int wc = workerCountOf(c);
            // 如果wc超过CAPACITY,也就是ctl的低29位的最大值(二进制是29个1),返回false;
            // 这里的core是addWorker方法的第二个参数,如果为true表示根据corePoolSize来比较,
            // 如果为false则根据maximumPoolSize来比较。
            //
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            // 尝试增加workerCount,如果成功,则跳出第一个for循环
            if (compareAndIncrementWorkerCount(c))
                break retry;
            // 如果增加workerCount失败,则重新获取ctl的值
            c = ctl.get();  // Re-read ctl
            // 如果当前的运行状态不等于rs,说明状态已被改变,返回第一个for循环继续执行
            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 {
        // 根据firstTask来创建Worker对象
        w = new Worker(firstTask);
        // 每一个Worker对象都会创建一个线程
        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());
                // rs < SHUTDOWN表示是RUNNING状态;
                // 如果rs是RUNNING状态或者rs是SHUTDOWN状态并且firstTask为null,向线程池中添加线程。
                // 因为在SHUTDOWN时不会在添加新的任务,但还是会执行workQueue中的任务
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    // workers是一个HashSet
                    workers.add(w);
                    int s = workers.size();
                    // largestPoolSize记录着线程池中出现过的最大线程数量
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                // 启动线程
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93

6、线程的执行过程

6.1、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为空,则通过getTask来获取任务

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

    }
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92

这里说明一下第一个if判断,目的是:

  • 如果线程池正在停止,那么要保证当前线程是中断状态;
  • 如果不是的话,则要保证当前线程不是中断状态;

总结一下runWorker方法的执行过程:

  1. while循环不断地通过getTask()方法获取任务;
  2. getTask()方法从阻塞队列中取任务;
  3. 如果线程池正在停止,那么要保证当前线程是中断状态,否则要保证当前线程不是中断状态;
  4. 调用task.run()执行任务;
  5. 如果task为null则跳出循环,执行processWorkerExit()方法;
  6. runWorker方法执行完毕,也代表着Worker中的run方法执行完毕,销毁线程。

这里的beforeExecute方法和afterExecute方法在ThreadPoolExecutor类中是空的,留给子类来实现。

completedAbruptly 变量来表示在执行任务过程中是否出现了异常,在processWorkerExit 方法中会对该变量的值进行判断。


Thread的run方法实际上调用了 Worker 类的 runWorker 方法,而 Worker 类继承了 AQS 类,并实现了 lockunlocktrylock 方法。但是这些方法不是真正意义上的锁,所以在代码中加锁操作和解锁操作没有成对出现。

runWorker 方法中获取到任务就“加锁”,完成任务后就“解锁”。也就是说在“加锁”到“解锁”的这段时间内,线程处于忙碌状态,而其它时间段,处于空闲状态。线程池就可以通过 trylock 方法来确定这个线程是否空闲。

getTask 方法的主要作用是从阻塞队列中获取任务。

beforeExecute(wt, task)afterExecute(task, thrown) 是个钩子函数,如果我们需要在任务执行之前和任务执行以后进行一些操作,那么我们可以自定义一个继承 ThreadPoolExecutor 类,并覆盖这两个方法。

6.2、getTask 源代码

private Runnable getTask() {
    // timeOut变量的值表示上次从阻塞队列中取任务时是否超时
    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.
        /*
         * 如果线程池状态rs >= SHUTDOWN,也就是非RUNNING状态,再进行以下判断:
         * 1. rs >= STOP,线程池是否正在stop;
         * 2. 阻塞队列是否为空。
         * 如果以上条件满足,则将workerCount减1并返回null。
         * 因为如果当前线程池状态的值是SHUTDOWN或以上时,不允许再向阻塞队列中添加任务。
         */
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }
        int wc = workerCountOf(c);
        // Are workers subject to culling?
        // timed变量用于判断是否需要进行超时控制。
        // allowCoreThreadTimeOut默认是false,也就是核心线程不允许进行超时;
        // wc > corePoolSize,表示当前线程池中的线程数量大于核心线程数量;
        // 对于超过核心线程数量的这些线程,需要进行超时控制
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
         
        /*
         * wc > maximumPoolSize的情况是因为可能在此方法执行阶段同时执行了setMaximumPoolSize方法;
         * timed && timedOut 如果为true,表示当前操作需要进行超时控制,并且上次从阻塞队列中获取任务发生了超时
         * 接下来判断,如果有效线程数量大于1,或者阻塞队列是空的,那么尝试将workerCount减1;
         * 如果减1失败,则返回重试。
         * 如果wc == 1时,也就说明当前线程是线程池中唯一的一个线程了。
         */
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }
        try {
            /*
             * 根据timed来判断,如果为true,则通过阻塞队列的poll方法进行超时控制,如果在keepAliveTime时间内没有获取到任务,则返回null;
             * 否则通过take方法,如果这时队列为空,则take方法会阻塞直到队列不为空。
             *
             */
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            // 如果 r == null,说明已经超时,timedOut设置为true
            timedOut = true;
        } catch (InterruptedException retry) {
            // 如果获取任务时当前线程发生了中断,则设置timedOut为false并返回循环重试
            timedOut = false;
        }
    }
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58

可以看出如果允许线程在keepAliveTime时间内未获取到任务线程就销毁就调用workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS),否则会调用workQueue.take()方法(该方法即使获取不到任务就会一直阻塞下去)。而确定是否使用workQueue.poll方法只有两个条件决定,一个是当前池中的线程是否大于核心线程数量,第二个是是否允许核心线程销毁,两者其一满足就会调用该方法。

7、 线程池的关闭过程

7.1、shutdown源码

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(SHUTDOWN);
        interruptIdleWorkers();
        onShutdown();// hook for ScheduledThreadPoolExecutor
    }finally {
        mainLock.unlock();
    }
    tryTerminate();
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

advanceRunState(SHUTDOWN) 的作用是通过CAS操作将线程池的状态更改为 SHUTDOWN 状态。
interruptIdleWorkers 是对空闲的线程进行中断,它实际上调用了重载带参数的函数 interruptIdleWorkers(false)

onShutdown 也是一个钩子函数

7.2、interruptIdleWorkers 源码

private void interruptIdleWorkers(boolean onlyOne) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers) {
            Thread t = w.thread;
            if (!t.isInterrupted() && w.tryLock()) {
                try {
                    t.interrupt();
                }catch (SecurityException ignore) {
                }finally {
                    w.unlock();
                }
            }
            if (onlyOne)
                break;
        }
    }finally {
        mainLock.unlock();
    }
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

通过workers容器,遍历池中的线程,对每个线程进行tryLock()操作,如果成功说明线程空闲,则设置其中断标志位。而线程是否响应中断则由任务的编写者决定。

同类文章:

ThreadPoolExecutor : https://blog.csdn.net/djzhao/article/details/82192918

posted @ 2023-03-17 09:46  甜菜波波  阅读(446)  评论(0编辑  收藏  举报