深入源码剖析ThreadPoolExecutor的工作原理

一文搞懂ThreadPoolExecutor原理 Executor框架提供了组件来管理Java中的线程,Executor框架将其分为任务线程执行任务任务执行结果三部分。下面以表格形式对这三部分进行说明。项说明任务Executor框架提供了Runnable接口和Callable接口,任务需要实现这两个接口才能被线程执行线程执行任务Executor框架提供了接口Executor和继承于Executor的接口来定义任务执行机制。Executor框架中的线程池类和均实现了接口任务执行结果Executor框架提供了Future接口和实现了Future。 阅读详情

为什么要有线程池

先来说说什么是池化技术:

池化技术指的是提前准备一些资源,在需要时可以重复使用这些预先准备的资源。 池化技术的优点主要有两个:提前准备和重复利用。因为创建一个类需要经历复杂且耗时的操作,因此我们应该尽量复用已有的类,以确保程序的高效运行,当然如果能够提前创建这些类就再好不过了,而这些功能的实现依靠的就是池化技术。

什么是线程池:

线程池是线程使用的一种模式,它将线程和任务的概念分离开,使用线程来执行任务,并提供统一的线程管理和任务管理的实现方法,避免了频繁创建和销毁线程所带来的性能开销。

线程池有什么优点:

  • 优点1:复用线程,降低资源消耗

线程在创建时要开辟虚拟机栈、本地方法栈、程序计数器等私有线程的内存空间,而销毁时又要回收这些私有空间资源,而线程池创建了线程之后就会放在线程池中,减少了系统资源的消耗

  • 优点2:提高响应速度

线程池是复用已有线程来执行任务的,而线程是在有任务时才新建的,所以相比于线程来说,线程池能够更快的响应任务和执行任务。

  • 优点3:管控线程数和任务数

控制最大并发数:线程池可以创建固定的线程数,从而避免了无限创建线程的问题;控制任务最大数:线程池可以控制最大任务数,当任务超过一定数量之后,就会采用拒绝策略来处理多出的任务,从而保证了系统可以健康的运行。

  • 优点4:更多增强功能 线程池相比于线程来说提供了更多的功能,比如定时执行和周期执行等功能。

线程池3 个核心部分:

  • 线程集合:核心线程和工作线程
  • 阻塞队列:用于待执行任务排队
  • 拒绝策略处理器:阻塞队列满后,对任务处理进行

Executor框架结构图解

从JDK1.5开始,为了把工作单元与执行机制分离开,Executor框架诞生了,他是一个用于统一创建与运行的接口。Executor框架实现的就是线程池的功能。

3大部分

(1)任务。也就是工作单元,包括被执行任务需要实现的接口:Runnable接口或者Callable接口;

(2)任务的执行。也就是把任务分派给多个线程的执行机制,包括Executor接口及继承自Executor接口的ExecutorService接口。

(3)异步计算的结果。包括Future接口及实现了Future接口的FutureTask类。

使用示意图:

从一个例子入手

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SimpleThreadPool {

    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 10; i++) {
            Runnable worker = new WorkerThread(i);
            executor.execute(worker);
        }
        executor.shutdown(); 
        while (!executor.isTerminated()) { 
        }
        System.out.println("Finished all threads");
    }

}
class WorkerThread implements Runnable {

    private Integer count;

    public WorkerThread(Integer s){
        this.count=s;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+" Start. count = "+count);
        processCommand();
        System.out.println(Thread.currentThread().getName()+" End.");
    }

    private void processCommand() {
        try {
            Thread.sleep(new Random().nextInt(5000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
/**
pool-1-thread-1 Start. Command = 0
pool-1-thread-3 Start. Command = 2
pool-1-thread-2 Start. Command = 1
pool-1-thread-4 Start. Command = 3
pool-1-thread-5 Start. Command = 4
pool-1-thread-5 End.
pool-1-thread-5 Start. Command = 5
pool-1-thread-3 End.
pool-1-thread-3 Start. Command = 6
pool-1-thread-2 End.
pool-1-thread-2 Start. Command = 7
pool-1-thread-1 End.
pool-1-thread-1 Start. Command = 8
pool-1-thread-2 End.
pool-1-thread-2 Start. Command = 9
pool-1-thread-4 End.
pool-1-thread-5 End.
pool-1-thread-1 End.
pool-1-thread-3 End.
pool-1-thread-2 End.
Finished all threads
/

程序中我们创建了固定大小为五个工作线程的线程池。然后分配给线程池十个工作,因为线程池大小为五,它将启动五个工作线程先处理五个工作,其他的工作则处于等待状态,一旦有工作完成,空闲下来工作线程就会捡取等待队列里的其他工作进行执行。输出表明线程池中至始至终只有五个名为 "pool-1-thread-1" 到 "pool-1-thread-5" 的五个线程,这五个线程不随着工作的完成而消亡,会一直存在,并负责执行分配给线程池的任务,直到线程池消亡。

ThreadPoolExecutor使用详解

其实java线程池的实现原理很简单,说白了就是一个线程集合workerSet和一个阻塞队列workQueue。当用户向线程池提交一个任务(也就是线程)时,线程池会先将任务放入workQueue中。workerSet中的线程会不断的从workQueue中获取线程然后执行。当workQueue中没有任务的时候,worker就会阻塞,直到队列中有任务了就取出来继续执行。

参数

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

  • corePoolSize 线程池中的核心线程数,最开始的时候,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize, 即使当前有其他空闲线程能够执行新来的任务, 也会继续创建线程;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。
  • maximumPoolSize 线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize;当阻塞队列是无界队列, 则maximumPoolSize则不起作用, 因为无法提交至核心线程池的线程会一直持续地放入workQueue.
  • workQueue 用来保存等待被执行的任务的阻塞队列. 在JDK中提供了如下阻塞队列:
  • ArrayBlockingQueue: 基于数组结构的有界阻塞队列,创建时必须指定大小,按FIFO排序任务;
  • LinkedBlockingQueue: 基于链表结构的阻塞队列,是无界的,默认大小是 Integer.MAX_VALUE,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQueue;
  • SynchronousQueue: 一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,可以把它看成容量为0的队列,吞吐量通常要高于LinkedBlockingQueue;
  • PriorityBlockingQueue: 具有优先级的无界阻塞队列;

LinkedBlockingQueue比ArrayBlockingQueue在插入删除节点性能方面更优,但是二者在put(), take()任务的时均需要加锁,SynchronousQueue使用无锁算法,根据节点的状态判断执行,而不需要用到锁,其核心是Transfer.transfer()

关于阻塞队列的详细信息可以参考:ThreadPoolExecutor最佳实践--如何选择队列

  • keepAliveTime 线程空闲时的存活时间,即当线程没有任务执行时,该线程继续存活的时间;默认情况下,该参数只在线程数大于corePoolSize时才有用, 超过这个时间的空闲线程将被终止;
  • unit keepAliveTime的单位
  • threadFactory 创建线程的工厂,通过自定义的线程工厂可以给每个新建的线程设置一个具有识别度的线程名。默认为DefaultThreadFactory
  • handler 线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略:
  • AbortPolicy: 直接抛出异常,默认策略;
  • CallerRunsPolicy: 用调用者所在的线程来执行任务;
  • DiscardOldestPolicy: 丢弃阻塞队列中靠最前的任务,并执行当前任务;
  • DiscardPolicy: 直接丢弃任务;

推荐根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务,方便对线程池容量, 以及使用是否合理进行分析, 这一点在实战中特别重要。

三种类型

newFixedThreadPool(可重用固定线程数的线程池)

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

FixedThreadPool 的 corePoolSize 和 maximumPoolSize 都被设置为创建 FixedThreadPool 时指定的参数 nThreads。

 

  1. 如果当前运行的线程数少于 corePoolSize,则创建新线程来执行任务
  2. 在线程池完成预热之后(当前运行的线程数等于 corePoolSize),将任务加入 LinkedBlockingQueue
  3. 线程执行完1中的任务后,会在循环中反复从 LinkedBlockingQueue 获取任务来执行 FixedThreadPool 使用无界队列 LinkedBlockingQueue 作为线程池的工作队列(队列的容量为 Integer.MAX_VALUE)。使用无界队列作为工作队列会对线程池带来如下影响:
  1. 当线程池中的线程数达到 corePoolSize 后,新任务将在无界队列中等待,因此线程池中的线程数不会超过 corePoolSize
  2. 由于1,使用无界队列时 maximumPoolSize 将是一个无效参数
  3. 由于1和2,使用无界队列时 keepAliveTime 将是一个无效参数
  4. 由于使用无界队列,运行中的 FixedThreadPool 不会拒绝任务(不会调用 RejectedExecutionHandler.rejectedExecution 方法)

SingleThreadExecutor(使用单个 worker 线程的 Executor)

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

SingleThreadExecutor 的 corePoolSize 和 maximumPoolSize 被设置为 1。其他参数与 FixedThreadPool 相同。

  1. 如果当前运行的线程数少于 1,则创建一个新线程来执行任务
  2. 在线程池完成预热之后(当前线程池中有一个运行的线程),将任务加入 LinkedBlockingQueue
  3. 线程执行完 1 中的任务后,会在一个无限循环中反复从 LinkedBlockingQueue 获取任务来执行

CachedThreadPool(根据需要创建新线程的线程池)

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

CachedThreadPool 的 corePoolSize 被设置为 0,即 corePool 为空;maximumPoolSize 被设置为 Integer.MAX_VALUE,即 maximumPoolSize 是无界的。这里把 keepAliveTime 设置为60L,意味着 CachedThreadPool 中的空闲线程等待新任务的最长时间为60秒,空闲线程超过60秒后将会终止。 CachedThreadPool 使用没有容量的 SynchronousQueue 作为线程池的工作队列,但 maximumPool 是无界的。这意味着如果主线程提交任务的速度高于 maximumPool 中线程处理任务的速度时,CachedThreadPool 会不断创建新线程。极端情况下,CachedThreadPool 会因为创建过多线程而耗尽CPU和内存资源。

  1. 首先执行 SynchronousQueue.offer(Runnable task)。如果当前 maximumPool 中有空闲线程正在执行 SynchronousQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS),那么主线程执行 offer 操作与空闲线程执行的 poll 操作配对成功,主线程把任务交给空闲线程执行,execute() 方法执行完成;否则执行下面的步骤2
  2. 当初始 maximumPool 为空,或者 maximumPool 中当前没有空闲线程时,将没有线程执行 SynchronousQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)。这种情况下,步骤1将失败。此时 CachedThreadPool 会创建一个新线程执行任务,execut() 方法执行完成
  3. 在步骤2中新创建的线程将任务执行完成后,会执行 SynchronousQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)。这个 poll 操作会让空闲线程最多在 SynchronousQueue 中等待60秒钟。如果60秒钟内主线程提交了一个新任务(主线程执行步骤1),那么这个空闲线程将执行主线程提交的新任务;否则,这个空闲线程将终止。由于空闲60秒的空闲线程会被终止,因此长时间保持空闲的 CachedThreadPool 不会使用任何的资源。

关闭线程池

遍历线程池中的所有线程,然后逐个调用线程的interrupt方法来中断线程.

关闭方式 - shutdown

将线程池里的线程状态设置成SHUTDOWN状态(不再接受新任务,但可以继续执行队列中的任务), 然后中断所有没有正在执行任务的线程.

关闭方式 - shutdownNow

将线程池里的线程状态设置成STOP状态, 然后停止所有正在执行或暂停任务的线程. 只要调用这两个关闭方法中的任意一个, isShutDown() 返回true. 当所有任务都成功关闭了, isTerminated()返回true.

ThreadPoolExecutor源码详解

成员变量说明

// ctl存储线程池状态和线程数,integer共32位,那么用前三位表示线程池状态,后29位表示线程池数量。
// 初始化,状态为RUNNING,起始线程数是0。
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// Integer 共有 32 位,最右边 29 位表示工作线程数,最左边 3 位表示线程池状态
// 线程池的状态用高 3 位表示,其中包括了符号位
private static final int COUNT_BITS = Integer.SIZE - 3;
// 000-111111111111111111111111111111
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// 以下这五种状态依次为从小到大
// 十进制值:-536870912
// 此状态表示线程池可以接受新任务
private static final int RUNNING = -1 << COUNT_BITS;
// 十进制值:0
// 此状态不再接受新任务,但可以继续执行队列中的任务
private static final int SHUTDOWN = 0 << COUNT_BITS;
// 十进制值:536870912
// 此状态全面拒绝,并中断正在处理的任务
private static final int STOP = 1 << COUNT_BITS;
// 十进制值:1073741824
// 此状态表示所有任务已经被终止
private static final int TIDYING = 2 << COUNT_BITS;
// 十进制值:1610612736
// 此状态表示已清理完现场
private static final int TERMINATED = 3 << COUNT_BITS;
// 获取当前线程池的状态(前3位)
private static int runStateOf(int c) { return c & ~CAPACITY; }
// 获取当前线程池中线程数(后29位)
private static int workerCountOf(int c){ return c & CAPACITY; }
// 更新状态和数量
private static int ctlOf(int rs, int wc) { return rs | wc; }
// 小于判断C是不是小于S,比如runStateLessThan(var,STOP),那var就只有可能是(RUNNING,SHUTDOWN)
private static boolean runStateLessThan(int c, int s) {
    return c < s;
}
// 是不是C >= S
private static boolean runStateAtLeast(int c, int s) {
    return c >= s;
}
// 判断状态是不是RUNNING
private static boolean isRunning(int c) {
    return c < SHUTDOWN;
}
// execute()方法提交的Runnable任务,如果当前没有获取到线程去执行任务,那么任务将放到这个阻塞队列中.
private final BlockingQueue<Runnable> workQueue;
// 这个锁用来保护下面的workers,访问workers必须获取这个锁.
private final ReentrantLock mainLock = new ReentrantLock();
/** 
设置包含池中的所有工作线程, 只有在持有主锁时才能访问.
---> 这里为什么不使用线程安全的数据结构的原因主要两个:
1\. 有复合操作, 增加 worker 的同时还要更新 largestPoolSize.
2\. 中断线程时,如果不加锁,就可能出现并发的中断线程,引起中断风暴.
*/
private final HashSet<Worker> workers = new HashSet<Worker>();
// 线程通信手段, 用于支持awaitTermination方法, awaitTermination的作用等待所有任务完成,并支持设置超时时间,返回值代表是不是超时.
private final Condition termination = mainLock.newCondition();
// 记录workers历史以来的最大值,每次获取之前都要获取主锁mainlock
// 每次增加worker的时候,都会判断当前workers.size()是否大于largestPoolSize,如果大于,则将当前最大值赋予largestPoolSize.
private int largestPoolSize;
// 计数所有已完成任务,每次获取之前都要获取主锁mainlock
// 每个worker都有一个自己的成员变量 completedTasks 来记录当前 worker 执行的任务次数, 当前线worker工作线程终止的时候, 才会将worker中的completedTasks的数量加入到 completedTaskCount 指标中.
private long completedTaskCount;
// 线程工厂,用于构造线程的时候加一些业务标识什么的.
private volatile ThreadFactory threadFactory;
// 拒绝策略,默认四种AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy,建议自己实现,增加监控指标.
private volatile RejectedExecutionHandler handler;
// 当线程池内线程数超过corePoolSize之后,空闲的线程多久之后进行销毁.
private volatile long keepAliveTime;
// 核心线程池空闲允许销毁的时间.
private volatile boolean allowCoreThreadTimeOut;
// 线程池核心线程池大小
private volatile int corePoolSize;
// 线程池可建立的最大线程数
private volatile int maximumPoolSize;
// 默认拒绝策略 AbortPolicy
private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();
//  安全控制访问(主要用于shutdown和 shutdownNow方法)
private static final RuntimePermission shutdownPerm = new RuntimePermission("modifyThread");
// threadPoolExecutor初始化的时候,还会初始化AccessControlContext对象的acc的值。
// 在threadPoolExecutor初始化的时候赋值,acc对象是指当前调用上下文的快照,其中包括当前线程继承的AccessControlContext和任何有限的特权范围,使得可以在稍后的某个时间点(可能在另一个线程中)检查此上下文。
private final AccessControlContext acc;

重点分析一下线程池运行状态-runState:

运行状态状态描述
RUNNING接收新任务,并且也能处理阻塞队列中的任务。
SHUTDOWN不接收新任务,但是却可以继续处理阻塞队列中的任务。
STOP不接收新任务,同时也不处理队列任务,并且中断正在进行的任务。
TIDYING所有任务都已终止,workercount(有效线程数)为0,线程转向 TIDYING 状态将会运行 terminated() 钩子方法。
TERMINATEDterminated() 方法调用完成后变成此状态。

生命周期状态流转如下图所示:

提交任务主流程分析

在 ThreadPoolExcute 内部有几个重要的检验,看源码的时候不妨留心观察一下:

  • 判断当前的运行状态,根据运行状态来做处理,如果当前都停止运行了,那很多操作也就没必要了
  • 判断当前线程池的数量,然后将该数据和 corePoolSize 以及 maximumPoolSize 进行比较,然后再去决定下一步该做啥

AbstractExecutorService.submit()

public <T> Future<T> submit(Callable<T> task) {
    if (task == null) throw new NullPointerException();
    // 构造一个FutureTask实例,只要作用是获得返回值.这部分先不管,先看提交过程
    RunnableFuture<T> ftask = newTaskFor(task);
    //执行任务
    execute(ftask);
    return ftask;
}

关于FutureTask的作用可以看看我的这篇文章:走进源码让你搞明白什么是FutureTask,这里不做过多赘述。

ThreadPoolExecutor.execute()

先看流程图:

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    // 返回包含线程数及线程池状态的 Integer 类型数值
    int c = ctl.get();
    //  第一步,如果工作线程数小于核心线程数,则创建线程任务并执行
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true)) {//#2
            return;
        }
        // 如果创建失败,防止外部已经在线程池中加入新任务,重新获取线程数及线程池状态
        // 创建失败的原因在下面分析addWorker的时候有说到
        c = ctl.get();
    }
    // 第二步,超过核心线程数则将该任务提交到阻塞队列(如果不违反容量限制且操作成功后返回true,否则返回false不报异常),只有线程池处于 RUNNING(线程池可以接受新任务) 状态,才执行置入队列的操作
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();//double check
        // 如果线程池不是 RUNNING 状态,则将刚加入队列的任务移除
        if (!isRunning(recheck) && remove(command)) {
            //执行拒绝策略
            reject(command);
        }
        // 如果之前的线程已被消费完,新建一个线程
        else if (workerCountOf(recheck) == 0) {
            addWorker(null, false);//#3
        }
    }
    // 第三步,核心池和队列都已经满了,尝试创建一个新线程
    else if (!addWorker(command, false)) {//#4
        // 如果 addWorker 返回的是 false,表示创建失败,就会唤醒拒绝策略
        reject(command);
    }
}

分析:线程池的执行步骤及思路

  1. 如果线程池中的线程数少于corePoolSize,则尝试通过addWorker方法来新建一个线程执行提交的任务。 在执行addWorker方法时会原子地检查runState和workerCount,通过返回false来防止在不应该添加线程的情况下发出错误警报。因为存在多个线程并发的提交任务,所以需要在addWorker内部确保正确的创建线程。
  2. 如果线程池中的线程数超过corePoolSize则尝试将该任务提交到阻塞队列。
  3. 如果无法将任务添加到阻塞队列,则尝试新建线程处理这个任务,如果addWorker方法返回false则代表线程池已经饱和或者线程池已经关闭了,然后拒绝该任务。

为什么需要double check线程池的状态?

在多线程环境下,线程池的状态时刻在变化,而ctl.get()是非原子操作,很有可能刚获取了线程池状态后线程池状态就改变了。倘若没有double check,万一线程池处于非running状态(在多线程环境下很有可能发生),没有double check的情况下就不会将command移出,那么command永远不会执行。

我们从下面这个条件中

if (isRunning(c) && workQueue.offer(command))

可以得到的是至少在执行workQueue.offer(command))这段代码前线程池是在运行的,但是不能保证在执行这段代码时线程池不被其他线程关闭,所以这个if语句为true的情况有一下两种:

  1. 核心线程已满任务成功排队后线程池依旧正在运行
  2. 核心线程已满任务成功排队前线程池就被关闭了

如果是第二种情况的话这个任务相当于错误的被提交到线程池了,违反了关闭状态下的线程池不允许提交任务这个规则。因此需要进行二次判断(recheck)这个任务是否需要被回滚:

// 如果线程池不是 RUNNING 状态,则将刚加入队列的任务移除
if (!isRunning(recheck) && remove(command)) {
   //执行拒绝策略
   reject(command);
}

如果! isRunning(recheck) == true成立代表自进入外层的if以来有其它线程关闭了线程池。因此在这种并发情况下需要尝试对这个任务进行回滚,注意:是尝试回滚,不一定能够回滚成功。回滚调用的是内部的remove方法:

public boolean remove(Runnable task) {
   boolean removed = workQueue.remove(task);
   tryTerminate();
   return removed;
}

内部调用的是队列的remove方法将这个任务从队列中移除,但是不能保证这个任务一定能够被成功移除,如果移出失败或者不存在这个任务的情况下会返回false。考虑以下情况:

  1. A线程执行execute时发现此刻线程池的核心线程满了,然后进入外层的if (isRunning(c) && workQueue.offer(command))走到提交任务但是还未提交成功,此刻被cpu挂起
  2. B线程得到时间片调用了shutdown方法(具体流程看后文介绍)准备关闭线程池,将线程池的状态设置为SHUTDOWN然后被cpu挂起
  3. A线程恢复,任务提交成功入队然后又被挂起
  4. B线程恢复接着运行shutdown方法唤醒了线程池中那些空闲的线程然后这些空闲的线程从队列中取出了刚刚A线程刚刚提交的任务开始执行,此刻被挂起
  5. A线程继续执行内层的if (! isRunning(recheck) && remove(command))判断,发现线程池被关闭了(B线程关闭的)并且尝试调用remove方法失败,因为任务被线程B唤醒的线程拿去执行了,所以remove找不到这个任务返回false,这也就意味着被提交的任务由于其它线程关闭了线程池导致这个本应该被remove的任务却被错误的执行了,所以shutdown状态下的线程池也是有可能存在提交并成功执行任务的。

如果没有出现上面说的这种情况也就是说任务被正常remove就通过reject方法来拒绝这个被错误提交的任务(具体reject策略靠人为实现)。


但是如果remove失败了呢?就会走到else if (workerCountOf(recheck) == 0) ,走到这里意味着任务回滚失败(remove失败)或者任务入队前后线程池都处于运行状态,但是这种情况下为什么要判断当前线程池的线程数是否为0呢?考虑一下情况:

  1. A线程执行execute方法提交了一个任务T并成功入队,但是此刻线程池中的所有核心线程都正在执行耗时的任务,所以此刻任务队列中只存在一个任务T,然后被挂起
  1. B线程调用了shutdownNow方法将线程池的状态变更为STOP然后内部会调用interruptWorkers方法将所有的的核心线程的中断标记设置为true
  1. CPU调度B线程与A线程同时被挂起(还未执行shutdownNow方法中的drainQueue方法),此时线程池中的所有核心线程刚刚将那些耗时的任务都执行完毕,接着runWorker方法中的while循环就会继续执行getTask方法去队列中拿任务

因为上面B线程调用shutdownNow方法将线程池的状态设置为STOP了,所以接下来所有的核心线程都无法从队列中获取到任务,然后就会跳出runWorker的while循环,最终所有的核心线程将全部被移除,此刻核心线程池中一个worker都没有了,而线程池中的任务队列还存在一个A线程提交的任务T,这样就会导致这个任务永远无法被执行。所以在上面的else if中才会对当前线程池中的worker进行非零判断避免这种极端情况的发生,保证线程池中至少有一个worker在工作。

ThreadPoolExecutor.addWorker(Runnable firstTask, boolean core)

返回前面的线程池的 execute() 方法的代码中,可以发现这个addWorker() 有三个地方在调用,分别在 #2,#3和#4。

  • 2:当工作线程数 < 核心线程数的时候,通过addWorker(command, true)添加核心线程执行command任务。

  • 3:double check的时候,如果发现线程池处于正常运行状态但是里面没有工作线程,则添加个空任务和一个普通线程,这样一个 task 为空的 worker 在线程执行的时候会去阻塞任务队列里拿任务,这样就相当于创建了一个新的线程,只是没有马上分配任务。

  • 4:队列已满的情况下,通过添加普通线程(非核心线程)去执行当前任务,如果失败了则执行拒绝策略。

private boolean addWorker(Runnable firstTask, boolean core) {
    //第一步:检查是否应该创建一个新的线程来处理这个任务
    // 不需要任务预定义的语法标签,响应下文的 continue retry,快速退出多层嵌套循环
    // 此中写法类似于 goto 作用,目的是早实现多重循环时能够快速退出到任何一层
    retry:
    //使用了嵌套自旋来进行判断,外层自旋判断当前线程池状态
    for (; ; ) {
        int c = ctl.get();// 获取当前线程池状态及线程数的容器
        int rs = runStateOf(c);// 获取当前运行状态
        // 逻辑判断成立可以分为以下几种情况直接返回 false,表示添加工作线程失败:
        // rs > shutdown:线程池状态处于 STOP,TIDYING,TERMINATED时,添加工作线程失败,不接受新任务。
        // rs >= shutdown && firstTask != null:线程池状态处于 SHUTDOWN,STOP,TIDYING,TERMINATED状态且worker的首个任务不为空时,添加工作线程失败,不接受新任务。
        //rs >= shutdown && workQueue.isEmppty:线程池状态处于 SHUTDOWN,STOP,TIDYING,TERMINATED状态且阻塞队列为空时,添加工作线程失败,不接受新任务。
        if (rs >= SHUTDOWN && !(rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty())) {
        //把判断条件类比成rs >= shutdown && (rs != shutdown || firstTask != null || workQueue.isEmpty())更好理解
            return false;
        }
        //内层自旋判断当前线程池中的数量是否合理
        for (; ; ) {
            int wc = workerCountOf(c);// 当前线程数
            // 如果超过最大允许线程数则不能再添加新的线程
            //工作线程数量是否超过可表示的最大容量(CAPACITY)
            //如果添加核心工作线程,是否超过最大核心线程容量(corePoolSize)
            //如果添加普通工作线程,是否超过线程池最大线程容量(maximumPoolSize)
            if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) {
                return false;
            }
            // 将当前活动线程数加一(原子性操作)
            if (compareAndIncrementWorkerCount(c)) {
                // 成功的话表示直接跳出与 retry 相邻的这个循环体,也就是跳出外面那层的for循环,从开始创建工作线程继续
                // 如果失败了有可能是其它线程也执行到这里了(compareAndIncrementWorkerCount方法只会在addWorker方法中被执行)
                break retry;
            }
            // 失败的话需要重将线程池状态与外层自旋时的状态作对比,因为导致cas失败的其它线程可能会修改线程池的状态
            c = ctl.get();
            // 判断当前运行状态是不是改变了
            if (runStateOf(c) != rs)
                // 能走到这里说明在上面将当前活动线程数加一(原子性操作)失败了
                // 如果条件为假,则说明状态没变,线程池还处于运行状态,继续在 for (;;) 循环内执行,进行cas操作
                // 如果状态不一致的话代表此刻其它线程改变了线程池的状态,这个时候就需要重新从外层自旋开始进行判断了,线程池这个时候很有可能已经被关闭了
                continue retry;
        }
    }

    // 第二步:在满足第一步的条件下创建新的线程,然后执行提交的任务
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    //外面一层的try...catch...finally主要是为了判断工作线程是否启动成功,如果内层try...catch...finally代码执行成功,即 worker 添加进线程池成功,workerAdded 标志位置为true,则启动 worker 中的线程 t.start(),同时将标志位 workerStarted 置为 true,表示线程启动成功。
    try {
        // 利用 Worker 构造方法中的线程池工厂创建线程,并封装工作线程 Worker 对象
        w = new Worker(firstTask);
        // 这是 Worker 中的属性对象 thread
        final Thread t = w.thread;
        if (t != null) {
            // 在进行 ThreadPoolExecutor 的敏感操作时
            // 需要持有主锁,避免在添加和启动线程时被干扰
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            //里面一层的try...catch...finally,当Worker实例中的 Thread 线程不为空的时候,开启一个独占锁ReentrantLock mainLock,防止其他线程也来修改操作。
            try {
                //获取线程池运行状态,由于在调用shutdown方法或者shutdownNow方法时也会先获取锁,但是不能保证执行addWorker方法的线程先获得这把锁,所以添加workers前需要先判断当前线程池的状态
                int rs = runStateOf(ctl.get());
                // 当线程池处于 RUNNING 状态或者线程池处于 SHUTDOWN 状态但是当前线程的 firstTask 为空,满足以上条件时才能将 worker 实例添加进线程池
                if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) {
                    //因为worker的线程是通过ThreadFactory创建的,而调用线程的start方法先决条件是线程必须是未启动的(没有调用过start方法),所以如果ThreadFactory返回的是一个已启动的线程就需要抛出异常。
                    if (t.isAlive()) {
                        throw new IllegalThreadStateException();
                    }
                    workers.add(w);
                    int s = workers.size();
                    // 整个线程池在运行期间的最大并发任务个数
                    if (s > largestPoolSize) {
                        largestPoolSize = s;
                    }
                    //将标志位 workerAdded 设置为 true,表示添加工作线程成功。
                    workerAdded = true;
                }
            } finally {
                //无论成功与否,在 finally 中都必须执行 mainLock.unlock()来释放锁。
                mainLock.unlock();
            }
            if (workerAdded) {
                // t 是Worker中的属性对象 thread,相当于执行Worker的run方法
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (!workerStarted) {
            // addWorkerFailed方法主要是用来将这个未启动成功的worker从worker集中清除以及重新调整当前线程池中的线程数。
            addWorkerFailed(w);
        }
    }
    return workerStarted;
}

通过源码,可以总结出addWorker 失败的原因:

  1. 在添加时线程池被停止了
  2. 添加核心线程池时,超过核心线程池数量
  3. 添加非核心线程池时,超过最大线程池数量
  4. Worker 对象初始化未完成时就被使用,即 thread 对象还未完全创建
  5. 当前线程正在被运行(防止出现重复运行,抛出异常)
  6. 线程创建过多,导致 OOM

addWorkerFailed(Worker w)

清理未能成功启动的线程

private void addWorkerFailed(Worker w) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        if (w != null)
            workers.remove(w);
        decrementWorkerCount();
        tryTerminate();
    } finally {
        mainLock.unlock();
    }
}

addWorkerFailed的执行逻辑很简单,从worker集中移除这个worker,递减当前线程池中的线程数,然后尝试终止线程池。至于为什么要尝试终止线程池是因为我们不能保证这个启动失败的线程是因为什么原因启动失败的,甚至可能这个未能启动成功的线程运行时执行了shutdown或者shutdownNow方法,所以很有必要去检查尝试终止线程池。后面会详细分析tryTerminate方法。

Worker

将线程封装为 worker 主要是为了更好地管理线程的中断

理解 Worker 非常关键,主要有以下几点:

  1. Worker 很像是任务的代理,在线程池中,最小的执行单位就是 Worker,所以 Worker 实现了 Runnable 接口,实现了 run 方法;
  2. 在 Worker 初始化时 this.thread = getThreadFactory ().newThread (this) 这行代码比较关键,它把当前 Worker 作为线程的构造器入参,我们在后续的实现中会发现这样的代码:Thread t = w.thread;t.start (),此时的 w 是 Worker 的引用申明,此处 t.start 实际上执行的就是 Worker 的 run 方法;
  3. Worker 本身也实现了 AQS,所以其本身也是一个锁,其在执行任务的时候,会锁住自己,任务执行完成之后,会释放自己。
/**
 * 内部常量类,工作线程的核心类实现
 * 实现 Runnable 接口,并把本对象作为参数输入给 run() 方法中的 runWorker(this)
 * 所以内部属性线程 thread 在 start 的时候,即会调用 runWorker 方法
 * 继承 AQS 是为了实现独占锁这个功能
 */
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {

    private static final long serialVersionUID = 6138294804551838833L;

    final Thread thread;
    Runnable firstTask;
    volatile long completedTasks;

    /**
     * firstTask用它来初始化时传入的第一个任务,这个任务可以有也可以为null。如果这个值是非空的,那么线程就会在启动初期立即执行
     * 这个任务;如果这个值是null,那么就需要创建一个线程去执行任务列表(workQueue)中的任务,也就是非核心线程的创建。
     */
    Worker(Runnable firstTask) {
        // setState 是 AQS(AbstractQueuedSynchronizer) 的方法
        // 初始化为 -1,这样在线程运行前(调用runWorker)禁止中断,在 interruptIfStarted() 方法中会判断 getState()>=0
        setState(-1);
        this.firstTask = firstTask;
        //根据线程池的 threadFactory 创建一个线程,将 worker 本身传给线程(因为 worker 实现了 Runnable 接口)
        this.thread = getThreadFactory().newThread(this);
    }
    //....省略部分方法
    // 1 代表被锁住了,0 代表未锁
    protected boolean isHeldExclusively() {
        return getState() != 0;
    }

    // 尝试获取锁
    protected boolean tryAcquire(int unused) {
        // 从这里可以看出它是一个独占锁,因为当获取锁后,cas 设置 state 不可能成功,这里我们也能明白上文中将 state 设置为 -1 的作用,这种情况下永远不可能获取得锁,而 worker 要被中断首先必须获取锁
        if (compareAndSetState(0, 1)) {
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }
    // 尝试释放锁
    protected boolean tryRelease(int unused) {
        setExclusiveOwnerThread(null);
        setState(0);
        return true;
    }  
    // 中断线程,这个方法会被 shutdowNow 调用,从中可以看出 shutdownNow 要中断线程不需要获取锁,也就是说如果线程正在运行,照样会给你中断掉,所以一般来说我们不用 shutdowNow 来中断线程,太粗暴了,中断时线程很可能在执行任务,影响任务执行
    void interruptIfStarted() {
        Thread t;
        // 中断也是有条件的,必须是 state >= 0 且 t != null 且线程未被中断
        // 如果 state == -1 ,不执行中断,再次明白了为啥上文中 setState(-1) 的意义
        if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
            try {
                t.interrupt();
            } catch (SecurityException ignore) {
            }
        }
    }
}

为什么work要继承AQS?

在stack Overflow上找到的回答如下:

We implement a simple non-reentrant mutual exclusion lock rather than use ReentrantLock because we do not want worker tasks to be able to reacquire the lock when they invoke pool control methods like setCorePoolSize.

翻译如下:我们实现了一个简单的不可重入的互斥锁锁,而不是使用 ReentrantLock,因为我们不希望工作任务在调用诸如 setCorePoolSize 这样的池控制方法时能够重新获取锁。


这是因为可能正在等待任务的线程是通过未锁定来表示的,也就是说,tryLock 对于它们返回 true。如果在这里使用 ReentrantLock,会发生什么情况呢:

setCorePoolSize->interruptIdleWorkers->tryLock() (here success!) -> Thread.interrupt (thread of this worker)
复制代码

这将导致工作器中断本身。

来看一看setCorePoolSize这个方法:

/**
* 设置核心线程数。这会覆盖构造函数中设置的任何值。如果新值小于当前值,多余的现有线程将在下次空闲时终止。
* 如果更大,则将在需要时启动新线程以执行任何排队的任务。
*
* @param corePoolSize the new core size
* @throws IllegalArgumentException if {@code corePoolSize < 0}
* @see #getCorePoolSize
*/
public void setCorePoolSize(int corePoolSize) {
   if (corePoolSize < 0)
       throw new IllegalArgumentException();
   int delta = corePoolSize - this.corePoolSize;
   this.corePoolSize = corePoolSize;
   if (workerCountOf(ctl.get()) > corePoolSize)
       interruptIdleWorkers();
   else if (delta > 0) {
       // 我们真的不知道“需要”多少新线程。作为一种启发式方法,
       // 预先启动足够多的新workers(直到新的核心大小)来处理队列中当前的任务数量,
       // 但如果队列在此过程中变空,则停止。
       int k = Math.min(delta, workQueue.size());
       while (k-- > 0 && addWorker(null, true)) {
           if (workQueue.isEmpty())
               break;
       }
   }
}

走到interruptIdleWorkers方法:

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;
           if (!t.isInterrupted() && w.tryLock()) {
               try {
                   t.interrupt();
               } catch (SecurityException ignore) {
               } finally {
                   w.unlock();
               }
           }
           if (onlyOne)
               break;
       }
   } finally {
       mainLock.unlock();
   }
}

要点就在于这个tryLock方法了:

public boolean tryLock() {
  return tryAcquire(1);
}

protected boolean tryAcquire(int unused) {
   if (compareAndSetState(0, 1)) {
       setExclusiveOwnerThread(Thread.currentThread());
       return true;
   }
   return false;
}

如果我们是基于AQS实现的不可重入锁也就是上面这样的写法,那么当线程在执行任务的时候我们调用setCorePoolSize方法一直走到这里这个tryLock肯定是返回false的(在执行任务的worker的state是1,因为获取了锁,所以这里通过compareAndSetState(0, 1)方法想要获取锁是一定会失败的),也就是说在执行任务的线程是不会被中断的,但如果变成可重入锁的话那么这里就不是这样写的了,大家可以看一看ReentrantLock里面tryAcquire的写法每重入一次state+1,所以说即使当前线程在执行任务任然有可能被拿到锁,被拿到锁了的话照着上面方法的逻辑就会被中断,对于上面回答的分析到此就结束啦。interruptIdleWorkers方法在后面讲shutdown方法的时候会具体讲解,可以先看下去到时候再回过头来理解。

这里再插一嘴,如果理解了上面的问题,那么肯定就知道tryAcquire 方法是根据 state 是否是 0 来判断的,所以我们在构造worker的时候,也就是调用Worker(Runnable firstTask)这个方法生成worker对象的时候要setState(-1),就是为了是为了禁止在执行任务前对线程进行中断。

Woker.run()

在上面的addWorker方法中看到如果线程添加成功就启动t.start(),相当于执行Worker 的 run() 方法:

public void run() {
    runWorker(this);
}

runWorker(Worker w)

run() 方法中又调用了runWorker() 方法:

/**
 * Worker 对象作为输入参数,Worker 内部属性线程调用 start 的时候就会执行此方法。
 */
final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        while (task != null || (task = getTask()) != null) {
            w.lock();
            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);
    }
}

里面包含了三块try...catch...finally代码,为了便于理解将其拆分,先看第一块:

Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
// Worker继承了AQS,所以执行unlock实际上最终
// 执行的就是Worker的tryRelease的方法,而
// tryRelease始终返回true,方法内部仅仅时将AQS的state设置为0,
// 代表此刻线程才真正开始运行任务,
// 也就是说执行到这一行代码前都是不允许中断当前线程的,
// 注意这段代码执行完毕后AQS的state为0
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
    // 循环的判断任务(firstTask或从队列中获取的task)是否为空
    while (task != null || (task = getTask()) != null) {
        // Worker加锁,本质是AQS获取资源并且尝试CAS更新state由0更变为1
        w.lock();
        // 只有以下两种情况需要中断当前线程
        // 1、已经执行shutdownNow方法但还未中断当前worker线程
        // (shutdownNow方法中断当前线程的操作可能晚于任务执行)
        // 所以需要执行wt.interrupt()方法恢复该worker的线程的中断标记
        // 2、已经执行shutdownNow方法并且shutdownNow方法也成功中断了这个
        // worker的线程,由于Thread.interrupted()会重置线程的中断状态,
        // 所以需要执行wt.interrupt()方法恢复该worker的线程的中断标记
        if ((runStateAtLeast(ctl.get(), STOP) ||
             (Thread.interrupted() &&
              runStateAtLeast(ctl.get(), STOP))) &&
            !wt.isInterrupted())
            wt.interrupt();

            //此处省略了第二个try...catch...finally
    }
    // 走到这里说明某一次getTask()返回为null,线程正常退出
    completedAbruptly = false;
} finally {
     // 如果执行到这只有两种可能,一种是执行过程中异常中断了,一种是队列里没有任务了,从这里可以看出线程没有核心线程与非核心线程之分,哪个任务异常了或者正常退出了都会执行此方法,此方法会根据情况将线程数-1
    processWorkerExit(w, completedAbruptly);
}

第二个try...catch...finally

try {
   beforeExecute(wt, task);
   Throwable thrown = null;

    //此处省略了第三个try...catch...finally

} finally {
    task = null;
    w.completedTasks++;
    w.unlock();
}
复制代码

第三个try...catch...finally

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

上面的代码中可以看到有beforeExecute、afterExecute和terminaerd三个函数,它们都是钩子函数,可以分别在子类中重写它们用来扩展ThreadPoolExecutor,例如添加日志、计时、监视或者统计信息收集的功能。

  • beforeExecute():线程执行之前调用
  • afterExecute():线程执行之后调用
  • terminaerd():线程池退出时候调用

这样拆分完之后发现,其实主要注意两个点就行了,分别是getTask()和task.run(),task.run()就是运行任务,那我们继续来看下getTask()是如何获取任务的。

getTask()

要注意的是调用getTask方法的地方是一个while死循环, 只要getTask有返回值,那么就不会退出循环。退出循环就说明改销毁超过核心线程数的那部分线程了。

private Runnable getTask() {
    boolean timedOut = false; //最后获取任务是不是超时了

    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);//获取当前的运行状态

        //1.线程池状态是STOP,TIDYING,TERMINATED
        //2.线程池shutdown并且队列是空的.
        //满足以上两个条件之一则工作线程数wc减去1,然后直接返回null
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

        // 是否开启超时机制,相当于当前这个worker线程是否需要被淘汰的标记
        // 如果核心线程数允许超时,则timed为true,开启超时机制.
        // 如果核心线程数不允许超时,那么就看当前总线程数是不是>核心线程数,如果大于,则timed为true,当前woker开启超时机制.
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
        //allowCoreThreadTimeOut它的默认值为false,默认的情况下核心线程数量会一直保持,即使这些线程是空闲的它也是会一直存在的,而当设置为 true 时,线程池中 corePoolSize 线程空闲时间达到 keepAliveTime 也将销毁关闭。

        //1.工作线程数 > 最大线程数maximumPoolSize 或者timed == true && timedOut == true
        //2.工作线程数 > 1 或者队列为空 
        //同时满足以上两个条件则通过CAS把线程数减去1,同时返回null。CAS把线程数减去1失败会进入下一轮循环做重试
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        // 走到这里代表当前这个worker线程需要从队列中获取任务
        try {
            //timed代表当前这个worker是否需要在空闲指定的时间内被淘汰
            // 如果timed为true,通过poll()方法做超时拉取,keepAliveTime时间内没有等待到有效的任务,则返回null
            // 如果timed为false,通过take()做阻塞拉取,会阻塞到有下一个有效的任务时候再返回(一般不会是null)
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            // 注意take方法时肯定不会返回null的,这是Queue的特性,所以返回只要返回null就代表
            // 当前这个worker线程肯定是非核心线程并且超时了。那么下一次for循环极大概率是需要淘汰这个非核心线程的
            if (r != null)
                return r;
            // 代表这个worker线程是一个非核心超时的线程
            timedOut = true;
            // 如果当前worker线程在阻塞期间抛出InterruptedException
            // 仅仅忽略这次异常,执行下一次for循环重试获取任务
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

首先是第一个 if 判断,当运行状态处于非 RUNNING 状态,此外 rs >= STOP(线程池是否正在 stop)或阻塞队列是否为空。则将 workerCount 减 1 并返回 null。为什么要减 1 呢,因为此处其实是去获取一个 task,但是发现处于停止状态了,也就是没必要再去获取运行任务了,那这个线程就没有存在的意义了。后续也会在 processWorkerExit 将该线程移除。

第二个 if 条件目的是控制线程池的有效线程数量。由上文中的分析可以知道,在执行 execute 方法时,如果当前线程池的线程数量超过了 corePoolSize 且小于 maximumPoolSize,并且 workQueue 已满时,则可以增加工作线程,但这时如果超时没有获取到任务,也就是 timedOut 为 true 的情况,说明 workQueue 已经为空了,也就说明了当前线程池中不需要那么多线程来执行任务了,可以把多于 corePoolSize 数量的线程销毁掉,保持线程数量在 corePoolSize 即可。

什么时候会销毁?当然是 runWorker 方法执行完之后,也就是 Worker 中的 run 方法执行完,由 JVM 自动回收。

getTask 方法返回 null 时,在 runWorker 方法中会跳出 while 循环,然后会执行 processWorkerExit 方法。

processWorkerExit

分两种情况:

  • 任务异常退出, 则在加个worker回来, 当前任务是丢了的
  • 任务不是异常退出:
    1. 如何核心线程允许超时,当任务队列中还有任务,那么就必须保证线程池中有一个worker,没有就在这个方法里面执行addWorker.
    2. 如果核心线程不允许超时,就得保证当前线程池中线程数量>=核心线程数,如果当前线程池中线程数量<核心线程数,依然要增加一个worker,执行addWorker.

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    //如果completedAbruptly为true,则说明线程执行时出现异常,需要将workerCount数量减一
    //如果completedAbruptly为false,说明在getTask方法中已经对workerCount进行减一,这里不用再减  
    if (completedAbruptly) 
        decrementWorkerCount();

    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        completedTaskCount += w.completedTasks;
        // 加锁确保线程安全地移除 worker 
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }

    //尝试关闭线程池,但如果是正常运行状态,就不会关闭,这个是否关闭的临界条件在分析tryTerminate单独会说
    tryTerminate();

    int c = ctl.get();

    //这个地方比较绕,要好好看哈.
    // completedAbruptly=true代表异常结束的,具体为啥可以看runWorker中的代码,没有异常的话会走completedAbruptly=false的.
    //前提当前线程池的状态是SHUGTDOWN或者RUNNING,如果不是这两个状态,说明线程已经停止了,啥都不要干了.
    //如果任务是异常结束的,就增加worker
    //注: 别问我为啥上面要删除worker,还要再加,不删是不是不用加了。明确下那个任务已经退出getTask那块的死循环了,永远回不去了,回不去就说明没有任务可以执行了,只能先删掉然后再新增worker.
    if (runStateLessThan(c, STOP)) {
         // 如果不是发生异常被淘汰的
        if (!completedAbruptly) {
            // 线程池中允许存活的最小线程数
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            // 如果所有线程都允许超时并且此刻任务队列不为空则代表至少需要一个线程来执行剩余的所有任务
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
            // 如果此刻线程池中的线程数满足上面那个min值的话代表不需要额外创建新的worker来处理剩余的任务
            if (workerCountOf(c) >= min)
                return; // replacement not needed
        }
        addWorker(null, false);
    }
}

由此可见,线程池中线程的回收依赖JVM自动的回收,线程池做的工作是根据当前线程池的状态维护一定数量的线程引用,防止这部分线程被JVM回收,当线程池决定哪些线程需要回收时,只需要将其引用消除即可。

tryTerminate

final void tryTerminate() {
    for (;;) {
        int c = ctl.get();
        // 如果当前线程池状态处于运行状态或者线程池至少已经处于
        // TIDYING状态了(代表已经被终止了)或者线程池处于
        // SHUTDOWN状态(调用了shuntdown方法)但是任务还没有处理完的话
        // 这些情况下时还不需要终止线程池
        if (isRunning(c) ||
            runStateAtLeast(c, TIDYING) ||
            (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
            return;
        // 不是以上三种情况代表目前为止是有资格终止线程池的,
        // 如果此刻线程池中的线程数超过0个的话,则尝试终止一个空闲的线程,
        // interruptIdleWorkers会将第一个阻塞的线程唤醒,接下来这个线程就会跳出runWorker
        // 方法中的while循环最后执行processWorkerExit方法,则processWorkerExit方法会继续
        // 调用tryTerminate方法来确保传递终止信号
        if (workerCountOf(c) != 0) {
            interruptIdleWorkers(ONLY_ONE);
            return;
        }

        // 执行到这里代表线程池中的状态必定是处于SHUTDOWN或者STOP状态之一
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            // cas尝试将当前线程池状态设置为TIDYING将线程池中的线程数设置为0,
            // 成功的话则调用钩子方法terminated,因为存在并发调用tryTerminate方法
            // 所以只需要确保一个线程调用成功即可
            if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                try {
                    terminated();
                } finally {
                    // 最终将线程池状态设置为TERMINATED将线程池中的线程数设置为0
                    ctl.set(ctlOf(TERMINATED, 0));
                    // 线程池成功被终止后唤醒那些调用awaitTermination方法等待线程池终止的线程
                    termination.signalAll();
                }
                return;
            }
        } finally {
            mainLock.unlock();
        }
        // else retry on failed CAS
    }
}

在上面分析中我们只是分析了tryTerminate方法中断线程池的流程,tryTerminate方法只会在以下两种情况下尝试中断当前线程池

  1. 调用了shutdown方法之后并且线程池中的任务都被执行完成之后
  2. 调用了shutdownNow方法之后

那什么时候需要去调用这个方法呢?

作者在这个方法的注释如下

This method must be called following any action that might make termination possible -- reducing worker count or removing tasks from the queue during shutdown.

tryTerminate方法必须在任何可能导致终止的操作中之后被调用,例如减少线程池的worker数量或者当关闭线程池从队列中移除任务的时候。

最后再来思考一个问题:在 runWorker 方法中,执行任务时对 Worker 对象 w 进行了 lock 操作,为什么要在执行任务的时候对每个工作线程都加锁呢?

下面仔细分析一下:

  • 在 getTask 方法中,如果这时线程池的状态是 SHUTDOWN 并且 workQueue 为空,那么就应该返回 null 来结束这个工作线程,而使线程池进入 SHUTDOWN 状态需要调用shutdown 方法;
  • shutdown 方法会调用 interruptIdleWorkers 来中断空闲的线程,interruptIdleWorkers 持有 mainLock,会遍历 workers 来逐个判断工作线程是否空闲。但 getTask 方法中没有mainLock;
  • 在 getTask 中,如果判断当前线程池状态是 RUNNING,并且阻塞队列为空,那么会调用 workQueue.take() 进行阻塞;
  • 如果在判断当前线程池状态是 RUNNING 后,这时调用了 shutdown 方法把状态改为了 SHUTDOWN,这时如果不进行中断,那么当前的工作线程在调用了 workQueue.take() 后会一直阻塞而不会被销毁,因为在 SHUTDOWN 状态下不允许再有新的任务添加到 workQueue 中,这样一来线程池永远都关闭不了了;
  • 由上可知,shutdown 方法与 getTask 方法(从队列中获取任务时)存在竞态条件;
  • 解决这一问题就需要用到线程的中断,也就是为什么要用 interruptIdleWorkers 方法。在调用 workQueue.take() 时,如果发现当前线程在执行之前或者执行期间是中断状态,则会抛出 InterruptedException,解除阻塞的状态;
  • 但是要中断工作线程,还要判断工作线程是否是空闲的,如果工作线程正在处理任务,就不应该发生中断;
  • 所以 Worker 继承自 AQS,在工作线程处理任务时会进行 lock,interruptIdleWorkers 在进行中断时会使用 tryLock 来判断该工作线程是否正在处理任务,如果 tryLock 返回 true,说明该工作线程当前未执行任务,这时才可以被中断。

上述的分析中涉及到AQS的相关知识,可以看看我的这篇文章 万字长文带你玩转ReentrantLock,其中有讲解到AQS的知识。

关于线程回收的相关解析可以看看这篇 同事有话说:ThreadPoolExecutor是怎么回收线程的。

shutdown

shutdown方法的作用是尝试唤醒那些由于拿不到任务而阻塞在队列中或者还未正式运行的worker以便这些线程能够从队列中将任务都尽快的执行完。底层是通过设置线程的中断标志为true,这样的话基于AQS或者LockSupport来实现阻塞线程的同步类就不会阻塞线程,然后这些被设置中断标记的worker就能够持续不断的从阻塞队列中获取任务并执行任务直到任务队列为空最终线程池中的所有worker都将退出。

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(SHUTDOWN);
        interruptIdleWorkers();//调用 interruptIdleWorkers(false)
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
}

执行shutdown方法时必须获取主锁,然后调用checkShutdownAccess方法检查权限(不是重点)接下来advanceRunState方法修改线程池状态为SHUTDOWN

advanceRunState

private void advanceRunState(int targetState) {
    for (;;) {
        int c = ctl.get();
        if (runStateAtLeast(c, targetState) ||
            ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
            break;
    }
}

自旋判断当前线程池的状态是否至少为传入的targetState,此时传入的是SHUTDOWN,如果当前线程池已经处于SHUTDOWN的话就break跳出循环即可,否则需要通过cas尝试将当前线程池的ctl重新修改为方法传入的targetState与当前线程池的线程数,cas失败代表有其它线程正在修改ctl那么只需要在下次自旋时重新判断当前线程池的状态或者重新修改ctl即可。

将线程池状态修改为SHUTDOWN之后接着调用interruptIdleWorkers方法唤醒那些不是正在执行任务的worker(空闲的worker)

interruptIdleWorkers

interruptIdleWorkers(boolean onlyOne) 如果 ONLY_ONE = true 那么就的最多让一个空闲线程发生中断,ONLY_ONE = false 时是所有空闲线程都会发生中断。那线程什么时候会处于空闲状态呢?

一是线程数量很多,任务都完成了;二是线程在 getTask 方法中执行 workQueue.take() 时,如果不执行中断会一直阻塞。

所以每次在工作线程结束时调用 tryTerminate 方法来尝试中断一个空闲工作线程,避免在队列为空时取任务一直阻塞的情况。

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

interruptIdleWorkers方法的执行逻辑很简单,只要当前线程不是出于中断状态,然后调用worker的tryLock方法尝试获取worker的锁,还记得runWorker方法吗

while (task != null || (task = getTask()) != null) {
      w.lock();
}

worker在拿到任务的第一步就是给自己上锁代表worker当前正在执行任务,执行w.lock();这段代码后AQS的state就变成了1,而tryLock方法调用的是tryAcquire方法

protected boolean tryAcquire(int unused) {
    if (compareAndSetState(0, 1)) {
        setExclusiveOwnerThread(Thread.currentThread());
        return true;
    }
    return false;
}

很显然如果worker正在执行任务,这个tryAcquire方法必定时返回false的,所以interruptIdleWorkers方法会将那些不是正在执行任务的worker或者还未开始执行任务的worker(还没执行到runWorker中的while循环中)都设置为中断状态,这样的话这些被中断的线程在下次执行getTask方法调用队列的take获取poll方法拿任务的时候就不会被阻塞了,不懂原理的可以去看LockSupport和AQS相关的知识点。阻塞队列都是基于这两个东西实现的。接下来这些永远不会被阻塞的worker就会不停的消耗队列中的任务然后退出,接下来那些刚刚由于真正执行任务而没有设置中断标记的worker执行下一次getTask的时候会发现线程池的状态已经为SHUTDOWN了慢慢的在队列中的任务都被执行完毕之后就会进入getTask方法的这段退出代码中

if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
    decrementWorkerCount();
    return null;
}

最终线程池中的所有worker都将退出,任务都被执行完毕。

shutdownNow

shutdownNow方法的作用是中断当前线程池中所有已启动的worker线程,这样那些所有正在运行的线程在执行完任务后重新执行getTask方法时就会直接退出,我们看一下shutdownNow方法的代码

public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(STOP);
        interruptWorkers();
        tasks = drainQueue();
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
    return tasks;
}

interruptWorkers

private void interruptWorkers() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers)
            w.interruptIfStarted();
    } finally {
        mainLock.unlock();
    }
}

执行逻辑很简单,遍历线程池中所有的worker调用interruptIfStarted方法

void interruptIfStarted() {
    Thread t;
    if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
        try {
            t.interrupt();
        } catch (SecurityException ignore) {
        }
    }
}

这个中断方法也很简单,只要当前worker的AQS状态大于等于0(已启动)并且它自己本身没有被中断的话就将这个worker线程的中断标记设置为true。接下来那些正在运行的worker或者拿不到任务的worker在下一次执行getTask方法的时候退出

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

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

        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }
        //......省略部分代码
    }
}

在上面的getTask执行一开始会先判断当前线程池的状态,如果是STOP的话这个worker线程就会退出。同时如果当前线程池中存在拿不到任务而阻塞的线程,由于所有的worker的线程都被设置了中断标记,这些线程都会被唤醒或者抛出InterruptedException(LockSupport和AQS的原因)从而重新执行上面的自旋最终退出。

接下来我们继续看一下是如何将那些未完成的任务重现保存起来的。

drainQueue方法

private List<Runnable> drainQueue() {
    BlockingQueue<Runnable> q = workQueue;
    ArrayList<Runnable> taskList = new ArrayList<Runnable>();
    q.drainTo(taskList);
    if (!q.isEmpty()) {
        for (Runnable r : q.toArray(new Runnable[0])) {
            if (q.remove(r))
                taskList.add(r);
        }
    }
    return taskList;
}

drainQueue方法的执行逻辑也很简单,通过BlockingQueue自带的drainTo方法将内部剩余的所有任务都转移到另一个集合中,但是有一些特殊的队列例如DelayQueue是无法立即通过drainTo方法将任务进行转移的,因此可能需要通过remove方法尝试将这些任务强制删除然后转移到新队列中。

awaitTermination方法

awaitTermination方法的作用时阻塞当前线程直到当前线程池被关闭或者在指定的时间后超时。

public boolean awaitTermination(long timeout, TimeUnit unit)
    throws InterruptedException {
    long nanos = unit.toNanos(timeout);
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (;;) {
            if (runStateAtLeast(ctl.get(), TERMINATED))
                return true;
            if (nanos <= 0)
                return false;
            nanos = termination.awaitNanos(nanos);
        }
    } finally {
        mainLock.unlock();
    }
}

整个执行逻辑很简单,获取mainLock,判断当前线程池是否已经终止了以及传递的超时时间是否为0,否则的话就通过mainLock的termination这个Condition使当前线程阻塞指定的时间。注意termination.awaitNanos(nanos);方法返回的值代表的是方法传入的超时时间减去线程阻塞的耗时时间,也就是说整个awaitTermination如果返回true的话代表当前线程池能够在指定的超时时间内被终止,否则的话则代表经过指定的超时时间线程池还未被终止。

总结

ThreadPoolExecutor的主体部分已经分析完了,其中的难点在于execute方法,从任务被提交的那一刻开始就有可能存在其它线程并发的往线程池中提交任务甚至是关闭线程池、动态改变线程池的核心参数等等操作,因此需要在关键的地方通过CAS对当前线程池的核心线程数与最大线程数和线程池的状态进行判断,避免错误的提交和执行任务。

相关问题

为什么线程池不允许使用Executors去创建?

线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors各个方法的弊端:

  • newFixedThreadPool和newSingleThreadExecutor: 主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
  • newCachedThreadPool和newScheduledThreadPool: 主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。

线程池里的线程真的有核心线程和非核心线程之分?

事实上在ThreadPoolExecutor中,核心线程与非核心线程没有什么区别,它们只是在数量上有区别而已,很有可能一个执行任务过快的核心线程会由于另一个非核心线程执行一个耗时的任务导致在指定的keepAliveTime之后这个核心线程被移除,从而使刚刚那个非核心线程转换为核心线程。核心与非核心线程的超时机制以及这两种线程间的转换完全是在内部的getTask方法中进行判断的。

Telegram Bot Api使用教程 其中token为你的机器人token,method为telegram给定的方法,在获取群组chat_id那一步就使用了telegram的其中一个方法(getUpdates),其他方法后面会介绍。telegram发送消息的方式类似与钉钉机器人,都是向一个api发送http请求,而且对于同一个APItelegram支持GET和POST两种请求方式.请求的api格式如下。通常来说我们都需要让机器人在一个群组里工作,所以首先我们需要将机器人添加到我们指定的群组,在群组里发送随意消息并@这个机器人,比如... 阅读详情

相关推荐

稳定性性能系列之十五——系统稳定性监控体系建设:从指标到预警的完整方案

深入探讨Android系统稳定性监控体系的设计与实现,涵盖指标体系设计、数据采集方案、异常检测算法、预警响应机制,并提供完整的监控平台搭建实践。

Cheson的专栏 1360

ThreadPoolExecutor原理解析

成员变量ctl是Integer的原子变量,使用一个变量同时记录线程池状态和线程池中线程个数,假设计算机硬件的Integer类型是32位二进制标示,如下面代码所示,其中高3位用来表示线程池状态,后面29位用来记录线程池线程个数。当用户线程提交任务到线程池后,在线程池没有执行拒绝策略的情况下,用户线 程会马上返回,而提交的任务要么直接切换到线程池中的Worker线程来执行,要么先放入线程池的阻塞队列里面,稍后再由Worker线程来执行。

codeLife1993的博客 1061

Buck变换器中EA模块的优化设计与稳定性提升策略

本文深入探讨了Buck变换器中误差放大器(EA)模块的核心作用与优化设计。作为系统的“大脑”,EA模块的性能直接决定了电源的稳定性与动态响应。文章详细分析了EA的电路结构、工作原理,并重点阐述了通过优化增益、带宽和相位裕度来提升系统稳定性的策略,同时提供了实用的设计技巧与仿真验证方法。

weixin_29189381的博客 424

线程池ThreadPoolExecutor实现原理

引言 线程池:可以理解为缓冲区,由于频繁的创建销毁线程会带来一定的成本,可以预先创建但不立即销毁,以共享方式为别人提供服务,一来可以提供效率,再者可以控制线程无线扩张。合理利用线程池能够带来三个好处: 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。 但是要做到合理的利..

augfun的博客 3616

突破边缘AI算力瓶颈:YOLOv12模型剪枝与知识蒸馏联合优化全攻略

这套剪枝-蒸馏联合方案的优势在于其系统性和可靠性。它不是单一的技巧,而是一套完整的工程流程,确保了从研发到部署的无缝衔接。

m0_71589036的博客 219

工业仪表读数识别:YOLO目标检测 + OCR混合方案实战

仪表整体(meter):粗定位整只仪表,用于快速裁剪、排除背景干扰表盘区域(dial):精确的圆形/方形表盘区域,用于后续矫正和读数数字区域(display)/指针(pointer):数显表定位数码管区域,指针表可辅助定位指针为什么要分三级?因为现场仪表安装角度、距离、光照差异极大。先粗检再精检,比一步到位的准确率高很多,也方便后续做透视变换。工业仪表读数识别,技术路线的选择比算法精度更重要。选对了路线,事半功倍;选错了,怎么调都达不到验收标准。几条落地建议:指针表优先传统算法。

专注于Python爬虫开发,分享爬虫技巧、项目实战与反爬经验,使用Scrapy、BeautifulSoup等工具,解决数据抓取难题。 96

ThreadPoolExecutor工作原理及源码详解

ThreadPoolExecutor线程池原理以及源码解析

weixin_44991038的博客 1216

线程池详解(通俗易懂超级好)

目标 【理解】线程池基本概念 【理解】线程池工作原理 【掌握】自定义线程池 【应用】java内置线程池 【应用】使用java内置线程池完成综合案例 线程池 线程池基础 线程池使用 线程池综合案例 4.学员练习 5.线程池总结 概念介绍 1:什么是线程池 2:为什么使用线程池 3:线程池有哪些优势 什么是池 什么是线程池 线程池其实就是一种多线程处理形式,处理过程中可以将任务添加到队列中,然后在创建线程后自动启动这些任务。这里的线程就是我们前面学过的线程,这里的任务就是我们前面学过的实现了Runna.

拉格朗日的学习笔记 12万+

Java线程池ThreadPoolExecutor的实现原理详解,让面试官仰望

本文将分析Java线程池ThreadPoolExecutor的实现原理,掌握了实现原理能帮助你更好的优化程序的性能,避免很多错误用法。本文的代码量较少,主要讲原理,等你把原理完全弄明白了代码随便一写一大堆。 1. 线程池的状态 首先,线程池是一个有状态的对象。状态有以下几种: · RUNNING: 运行中。此时线程池能接受任务,并且会处理队列中的任务; · SHUTDOWN: 关闭中。此时,线程池不接受新任务,但是会处理队列中的任务; · STOP: 停止。此时线程池不接受新任务,也不会处理队列中

m0_62714732的博客 615

四足狗机器人硬件开发技术栈详解

本文系统介绍了四足狗机器人的硬件开发技术,重点分析了五大核心模块:机械结构(骨架设计)、动力系统(关节驱动)、感知系统(环境监测)、控制系统(决策中枢)和电源系统(能源管理)。文章详细阐述了各模块的选型标准、技术要点和协同设计原则,提供了从需求定位到组装调试的完整开发流程,并针对常见问题给出解决方案。随着材料轻量化、模块标准化和AI集成化的发展趋势,四足机器人硬件将向更灵活、更智能的方向演进,为工业巡检、应急救援等场景提供更强大的移动平台支持。

start_up_go的博客 284

Oracle11g下载与安装(windows)

一、Oracle11g下载 二、安装准备 三、执行安装步骤 四、测试连接 附:Oracle11g服务说明

欢迎订阅我的“JAVA全栈开发笔记(全)”专栏,价格优惠,仅需19.9元。 2万+

从新手到专家:彻底搞懂ThreadPoolExecutor任务队列工作原理(含源码剖析)

深入解析线程池的任务队列工作原理,掌握ThreadPoolExecutor的核心机制与适用场景。涵盖任务提交、队列策略、拒绝策略及源码级实现细节,提升并发编程能力。从新手到专家的进阶之路,值得收藏。

PixelFlow的博客 380

3D深度饼图的QT实现

首先,我们需要创建一个QT项目并创建一个窗口,用于显示我们的饼图。在QT Creator中,选择创建一个新的QT Widgets应用程序项目,并创建一个新的窗口类。在本文中,我将介绍如何使用QT实现一个3D深度饼图(Pie 3D)的可视化效果。在构造函数中,我们将创建一个饼图(QChartView)并设置饼图的数据。这是一个简单的示例,您可以根据需要自定义和扩展代码来创建更复杂的饼图。现在,我们已经完成了3D深度饼图的QT实现。您将看到一个具有相应数据的3D深度饼图显示在窗口中。3D深度饼图的QT实现。

CodeJolt的博客 277

JUC线程池-ThreadPoolExecutor

在探讨的细节之前,我们首先要理解为什么在Java并发编程中,线程池扮演着如此重要的角色。

左灯右行的爱情 1281

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

第十六章 Java多线程--线程池-ThreadPoolExecutor

ThreadPoolExecutor 是 Java 中 java.util.concurrent 包下提供的一个线程池实现类,它继承自 AbstractExecutorService 并实现了 ExecutorService 接口。ThreadPoolExecutor 提供了一个高级别的抽象来管理和执行任务,允许开发者自定义线程池的行为,包括但不限于线程池的大小、线程的存活时间、任务队列类型、线程工厂以及拒绝策略等

Lin_Miao_09的博客 1768

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

Java 面试题:从源码理解 ThreadPoolExecutor 线程池的参数和工作原理 --xunznux

代码检查是否需要创建新线程执行任务,首先判断当前工作线程数是否小于核心线程数 corePoolSize,如果是,则尝试创建新的核心线程。如果不能创建新线程,则尝试将任务放入队列中,并在任务入队后再次检查线程池状态,确保线程池仍在运行且有足够的线程处理任务。如果任务既不能入队也不能创建新的线程,则最终通过拒绝策略处理任务。

weixin_44615954的博客 1157

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

ThreadPoolExecutor的线程复用底层的源码了解下?

你知道的越多,不知道的也越多! 线程池源码1. 整体思路2. 学习源码 想必大家对线程池都有了解吧,必须掌握的知识点有:构造函数里的入参列表、常见有哪几种线程池以及使用场景、为什么要用线程池、线程池接受到一个任务后的处理流程等等。 本文对以上问题不做说明了,主要是想讲明白其中的“线程复用”底层的原理。 啰嗦一句: 看源码不要一上来就死磕每一行代码(相信我死磕撑不了几分钟),先掌握大体思路,然后再仔细品。 1. 整体思路 相信以下这张图大家都很熟悉了: 其实就是线程池接受任务的处理规则。那图里的规则哪来.

u014240299的专栏 313

Jdk线程池ThreadPoolExecutor源码解析

文章目录一、基础模型1.1 线程池任务的抽象 FutureTask1.1.1 FutureTask类层次结构1.1.2 FutureTask源码1.2 线程池的抽象: 从Excutor、ExecutorService到AbstractExecutorService1.2.1 线程池类层次结构1.2.2 线程池接口定义1.2.3 小结二、线程池工作原理2.1 线程池工作机制概述2.2 任务提交、执行...

温柔一cai刀 526

ThreadPoolExecutor源码解析

ctl: private static final int COUNT_BITS = Integer.SIZE - 3; //COUNT_BITS = 29 //-1 = 11111111111111111111111111111111 //RUNNING = 11100000000000000000000000000000,32位 private static final int RUNNING = -1 << COUNT_BITS; //ctl = RUNNING privat

couguolede的博客 290

ThreadPoolExecutor 线程池解析

1、ThreadPoolExecutor 功能介绍 1.1 为什么要使用executor 一般来说,在开发过程中,通过创建Runnable对象,然后交由相应的Thread实例去执行它们。但在大量使用多线程时会有以下问题: 如果需要控制Thread的状态,需要开发相应的代码,不是特别方便。 无法控制线程数量,如果线程过多,影响性能。 而在Executor中,我们将 线程的创建与执行进行分离。我们只需要创建一个Runnable或者Callable对象,而具体的执行。我们只需要交给Executor就行。至于合

qq_17821591的博客 390

jdk 线程池 threadPoolExecutor 源码剖析

public void execute(Runnable command) { if (command == null) throw new NullPointerException(); if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) { if (runState == RUNNING && workQueue.

犀利的博客 404

原理剖析(第 003 篇)ThreadPoolExecutor工作原理分析

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_33961829的博客 79

并发编程-ThreadPoolExecutor 源码解析

刚刚搭建完angular环境之后我就迫不及待的打开 Vs Code 来创建一个新的项目 但是这个时候出现了问题: 弹出的错误提示窗口: 通过站在巨人的肩膀上我解决了问题: 问题: 虽然配置了环境,但是我们在配置环境之后需要修改我们的h ...

立志欲坚不欲锐,成功在久不在速度 567

从零开始的ThreadPoolExecutor源码解析

目录1.基本参数 ThreadPoolExecutor在我们日常操作中经常使用,常见的那些使用基本使用方法和就不说了,这篇分析一下基本源码(其实是因为源码比较少) 1.基本参数 首先看一下定义的基本参数 private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); private static final int COUNT_BITS = Integer.SIZE - 3; private static

LitterLy的博客 393

Java多线程-ThreadPoolExecutor是怎么去执行一个任务的?源码分析

前言 前面一遍文章 我们看了下FutureTask的源码,知道了怎么样去获取一个任务的返回值,今天我们看下ThreadPoolExecutor。 ThreadPoolExecutor 看名词 我们就可以 看做是ThreadPool 和Executor的结合,大概意思我们也能知道就是线程池执行器,哈哈这翻译 真棒。这篇博文 会从源码的角度去分析下 一个线程任务 加入的线程池以后 是怎么被执行的~ 线程池 上面 说线程的时候 我们也说过 线程是系统中极其珍贵的资源,那我们要合理的使用他,所以有了线程池的出现,那

zxlp520的博客 1282

Java面试实战系列【并发篇】- ThreadPoolExecutor核心方法源码深度剖析

摘要 本文深入剖析了ThreadPoolExecutor的核心源码实现,重点关注execute()方法的完整执行流程。通过流程图和源码分析,揭示了线程池任务调度的三个关键阶段:1)优先创建核心线程(workerCount < corePoolSize);2)任务入队与双重检查机制确保状态一致性;3)尝试创建非核心线程或执行拒绝策略。文章详细解析了每个阶段的并发安全设计,包括CAS操作、双重检查模式等,展现了线程池在高并发场景下的健壮性保障。

weixin_53590296的博客 934
上一篇: java语法基础-java面向对象编程-包的定义及使用
下一篇: 看P8大佬肝完《微服务架构设计模式》的一点总结
Java技术那些事儿
博客等级 码龄5年 2306粉丝 2584原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值