Java并发-线程池深度解析看这篇就够了

1,799 阅读35分钟

前言

本篇博文已更新至个人博客。 本文的亮点在于:

  • ThreadPoolExecutor的依赖关系,作了细致的分解以宏观层面了解Doug Lea的设计理念。
  • 总结了ThreadPoolExecutor的核心流程,归纳了重要的结论。
  • ThreadPoolExecutor源码逐行解析。
  • 例举了场景,加深理解线程池工作原理。

由于ThreadPoolExecutor的代码逻辑和设计理念比较复杂,建议大家学习的时候,多思考,顺着代码的思路,多动动笔画一画才能加深理解。 本文部分内容摘抄自throwable作者的博文,有些部分作者已经总结的很好,没必要再重复发明轮子,在这里感谢作者。

提出问题

这些问题也是我当时的疑惑点,按本文的思路学习ThreadPoolExecutor,这些问题迎刃而解。

1、创建一个线程池需要哪些参数?

2、线程池中的状态都有哪些?是如何实现的?

3、线程池为什么需要工作队列?有哪几种工作队列?

4、AQS在线程池中扮演什么角色?它的作用是什么?

5、线程池在shutdown的时候,如何保证任务不丢失?如何保证任务正常执行完毕?

、线程池在shutdown之后还能提交任务进来嘛?线程池在shutdownNow之后还能提交任务进来吗?

9、ThreadPoolExecutor是如何控制工作线程能够重复利用的。

10、ThreadPoolExecutor的执行结果是如何到Futrue中的?

前置知识复习

线程状态

如下图所示,Java中线程可分为NEW,RUNABLE,RUNING,BLOCKED,WAITING,TIMED_WAITING,TERMINATED 共七个状态,一个状态是如何过渡到另一个状态图中标识的很清楚。

线程状态示意图

  • 初始状态(NEW) 实现Runnable接口和继承Thread可以得到一个线程类,new一个实例出来,线程就进入了初始状态。
  • 就绪状态(RUNNABLE) 就绪状态只是说你资格运行,调度程序没有挑选到你,你就永远是就绪状态。
    • 调用线程的**start()**方法,此线程进入就绪状态。
    • 当前线程sleep()方法结束,其他线程join()结束,等待用户输入完毕,某个线程拿到对象锁,这些线程也将进入就绪状态。
    • 当前线程时间片用完了,调用当前线程的yield()方法,当前线程进入就绪状态。
    • 锁池里的线程拿到对象锁后,进入就绪状态。
  • 运行中状态(RUNNING) 线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。
  • 阻塞状态(BLOCKED) 阻塞状态是线程阻塞在进入synchronized关键字(当然也包括ReentrantLock)修饰的方法或代码块(获取锁)时的状态。
  • 等待(WAITING) 处于这种状态的线程不会被分配CPU执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态。
  • 超时等待(TIMED_WAITING) 处于这种状态的线程不会被分配CPU执行时间,不过无须无限期等待被其他线程显示地唤醒,在达到一定时间后它们会自动唤醒。
  • 终止状态(TERMINATED) 当线程的run()方法完成时,或者主线程的main()方法完成时,我们就认为它终止了。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦终止了,就不能复生。在一个终止的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。

线程池的作用

1、减少了创建和销毁线程的次数,可以重复利用工作线程

2、规范化线程的使用,避免创建过多的线程导致耗尽系统资源

3、丰富的线程管理手段和结果控制

ThreadPoolExecutor的原理

由ThreadPool原理图所示,ThreadPoolExecutor的核心组件包括:

  • ThreadFactory:线程工厂,用于创建工作线程,默认的线程工厂是Executors.defaultThreadFactory()
  • workQueue:阻塞工作队列,常用的有ArrayBlockingQueueLinkedBlockingQueue
  • 工作线程池:内部包含了cw(core worker)aw(additional worker)
  • RejectedExecutionHandler:拒绝执行处理器

ThreadPool原理图

类的设计

ThreadPoolExecutor的类图。

ThreadPoolExecutor类图

Executor

Executor该接口提供了一种将任务提交与每个任务将如何运行的机制,包括线程执行,调度等。Doug Lea将线程的执行抽象为任务(task)任务由各种实现Executor的执行器执行,具体执行的内容在执行器中回调。

public interface Executor {

    /**
     * 在不远的将来执行用户提供的command。
     * command或许在一个新的线程中执行,或者在线程池中执行,或许在调用的线程中。
     * 具体的执行方式在Executor的实现类实现
     *
     * @param command runnable任务
     * @throws RejectedExecutionException 如果command不能被Execute接受
     * @throws NullPointerException comman是null
     */
    void execute(Runnable command);
}

ExecutorService

ExecutorService实现了Executor接口,它提供了一系列方法管理终止(manage termination)和用于追踪一个或多个异步任务最终返回Future的方法。ExecutorService可以关闭(shutdown),关闭之后会拒绝新的任务。定义了两种关闭ExecutorService的方法。

  • shutdown()启动一个有序关闭,在该关闭中先执行已提交的任务,单不接受任何新的任务。如果执行器已经关闭,再次调用该方法也不会产生影响。该方法不负责等待已提交任务完成执行,awaitTermination来完成此项职责,在执行器发起shutdown request之后,它将阻塞直到所有任务已完成,或者触发了timeout,或者线程被中断(interrupted),无论这三种情况中的哪一种先触发,都会解除阻塞。
  • shutdownNow()尝试停止所有正在执行的任务,忽略正在等待的任务,并返回正在等待执行的任务的列表。该方法不负责等待已提交任务完成执行,awaitTermination来完成此项职责。除了尽最大努力尝试停止处理正在执行的任务之外,没有任何保证。比如,终止线程的方法是通过Thread.interupt()方法实现,但是如果任务中没有处理过中断,则shutdownNow()也无计可施,只能等待任务自然执行完毕。

终止(termination)时,执行器中没有任务在执行,没有任务在等待执行,更没有任务可以提交进执行器。终止后应关闭执行器,释放资源。

public interface ExecutorService extends Executor {

    void shutdown();

    List<Runnable> shutdownNow();
    //执行器是否关闭
    boolean isShutdown();
		//在调用shutdown之后,所有任务都已经完成,则返回true.
    //如果不提前调用shutdown或者shutdownNow,该方法不可能返回true
    boolean isTerminated();

    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;
    //提交一个Callable任务,有返回值并返回一个Future对象代表任务执行的结果。Future的get方法将等待任务执行完毕。
    <T> Future<T> submit(Callable<T> task);
    //提交一个Runnable任务,并返回Futrue对象
    <T> Future<T> submit(Runnable task, T result);

    Future<?> submit(Runnable task);

    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;

    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
        throws InterruptedException;

    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;

    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

Futrue

Futrue代表了异步执行的结果。提供了检测执行完成的方法,等待完成的方法,重试完成方法等控制结果的方法。

public interface Future<V> {

    /**
     * 尝试取消执行这个任务.任务完成或者任务取消或者一些其他的原因会导致尝试失败。
     * 如果任务还未开始执行,取消成功,那么这个任务将永远不会执行。
     * 如果任务已经开始执行,取消成功,则mayInterruptIfRunning参数来决定是不是中断线程
     */
    boolean cancel(boolean mayInterruptIfRunning);
    /**
    *  如果任务在执行完成前被取消,则返回true
    */
    boolean isCancelled();

    /**
     * 如果任务正常执行完毕则返回true
     */
    boolean isDone();

    /**
     * 等待执行结果
     *
     * @return the computed result
     * @throws CancellationException 如果任务被取消
     * @throws ExecutionException 执行过程中出现异常
     * @throws InterruptedException 如果任务被中断
     */
    V get() throws InterruptedException, ExecutionException;

    /**
     * 在有限时间内等待执行结果
     *
     * @throws CancellationException 如果任务被取消
     * @throws ExecutionException 执行过程中出现异常
     * @throws InterruptedException 如果任务被中断
     * @throws TimeoutException 如果任务超时
     */
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

一般情况下常用的实现是FutureTask,它是一个可取消的异步执行结果集,它实现了Futrue接口中定义的功能。

public class FutureTask<V> implements RunnableFuture<V> {
    /**
     * 任务的运行状态,初始状态为NEW.  
     *
     * 可能的状态变换为:
     * NEW -> COMPLETING -> NORMAL
     * NEW -> COMPLETING -> EXCEPTIONAL
     * NEW -> CANCELLED
     * NEW -> INTERRUPTING -> INTERRUPTED
     */
    private volatile int state;
    private static final int NEW          = 0;
    private static final int COMPLETING   = 1;
    private static final int NORMAL       = 2;
    private static final int EXCEPTIONAL  = 3;
    private static final int CANCELLED    = 4;
    private static final int INTERRUPTING = 5;
    private static final int INTERRUPTED  = 6;

    /** The underlying callable; nulled out after running */
    private Callable<V> callable;
    /** 任务执行结果,get()方法获取的就是这个对象 */
    private Object outcome; // non-volatile, protected by state reads/writes
    /** The thread running the callable; CASed during run() */
    private volatile Thread runner;
    /** Treiber stack of waiting threads */
    private volatile WaitNode waiters; 
}

FutureTask类图

AbstractExecutorService

AbstractExecutorService提供了ExecutorService的默认实现,实现了submitinvokeAnyinvokeAll,通过FutureTask类对Runnable执行过程进行包装。

ThreadPoolExecutor

ThreadPoolExecutor是线程池实现类,它依赖了上面提到的所有类,也是本篇博文重点讲述的类。

总结

通过对ThreadPoolExecutor依赖的类的了解,更能从宏观上理解作者的设计思想。Executor并发执行的线程抽象成任务,交给任务执行器来执行ExecutorService定义了任务执行器的关闭和提交任务。

线程池状态

如下代码所示,线程池的状态分为了

  • RUNNING:接受新的任务和处理队列中的任务
  • SHUTDOWN:拒绝新的任务,但是处理队列中的任务
  • STOP:拒绝新的任务,不处理队列中的任务,并且中断在执行的任务
  • TIDYING:所有任务已经终止(terminated),workerCount=0,线程
  • TERMINATED:terminated已完成

线程池的状态按如下方式进行转换

  • RUNNING->SHUTDOWN 调用shutdown()方法
  • (RUNNING or SHUTDOWN) -> STOP,调用shutdownNow()
  • SHUTDOWN -> TIDYING,当队列和线程池都为空
  • STOP->TIDYING,线程池为空
  • TIDYING -> TERMINATED,当terminated() hook method 执行完毕,所有线程都在awaitTermination()中等待线程池状态到达TERMINATED。
//接受新的任务和处理队列中的任务
private static final int RUNNING    = -1 << COUNT_BITS;
//拒绝新的任务,但是处理队列中的任务
private static final int SHUTDOWN   =  0 << COUNT_BITS;
//拒绝新的任务,不处理队列中的任务,并且中断在执行的任务
private static final int STOP       =  1 << COUNT_BITS;
//所有任务已经终止(terminated),workerCount=0,线程
private static final int TIDYING    =  2 << COUNT_BITS;
//terminated已完成
private static final int TERMINATED =  3 << COUNT_BITS;

核心流程

任务提交核心流程

根据下图所示的任务提交核心流程,主要是ThreadPoolExecutor#execute()方法,核心流程总结为以下结论:

  • 核心线程数小于corePoolSize,则需要创建新的工作线程来执行任务
  • 核心线程数大于等于corePoolSize,需要将线程放入到阻塞任务队列中等待执行
  • 队列满时需要创建非核心线程来执行任务,所有工作线程(核心线程+非核心线程)数量要小于等于maximumPoolSize
  • 如果工作线程数量已经达到maximumPoolSize,则拒绝任务,执行拒绝策略

TIP:这里需要注意对核心线程非核心线程的理解,它们不是工作线程的状态,核心线程提出的目的,是为了尽量维持工作线程的数量在corePoolSize

ThreadPoolExecutor#execute方法核心逻辑

创建工作线程核心流程

根据下图对java.util.concurrent.ThreadPoolExecutor#addWorker的分析,可以得出如下结论:

  • 只有工作线程被添加到线程池中并且工作线程start,才算添加成功,否则Worker对象只是一个临时对象(被GC掉)
  • 状态为STOPTIDYINGTERMINATED这三种时,是不会增加工作线程的
  • 状态为SHUTDOWN时是一个重要的边界条件

ThreadPoolExecutor#addWorker(Runnable firstTask, boolean core)

任务执行核心流程

如下图是任务执行的流程,可以总结如下:

  • 提交任务时如果工作线程数量小于核心线程数量,则firstTask != null,一路顺利执行然后阻塞在队列的poll上。
  • 提交任务时如果工作线程数量大于等于核心线程数量,则firstTask == null,需要从任务队列中poll一个任务执行,执行完毕之后继续阻塞在队列的poll上。
  • 提交任务时如果工作线程数量大于等于核心线程数量并且任务队列已满,需要创建一个非核心线程来执行任务,则firstTask != null,执行完毕之后继续阻塞在队列的poll上,不过注意这个poll是允许超时的,最多等待时间为keepAliveTime
  • 工作线程在跳出循环之后,线程池会移除该线程对象,并且试图终止线程池(因为需要考量shutdown的情况)
  • ThreadPoolExecutor提供了任务执行前和执行后的钩子方法,分别为beforeExecuteafterExecute
  • 工作线程通过实现AQS来保证线程安全(每次执行任务的时候都会lockunlock

任务执行核心流程

线程池关闭核心流程

如下图是shutdown方法的流程图,它是一种“温和“的关闭方式,执行完shutdown之后,会把线程状态置为SHUTDOWN,线程池并不会立即关闭,而是先中断能中断的工作线程,有些工作线程正在执行任务中,那么就先不中断,等待它们执行完毕之后中断工作线程。

java.util.concurrent.ThreadPoolExecutor#shutdown流程图

如下图是shutdownNow的流程图,与shutdown不同的地方在于,它关闭线程池的方式比较“粗暴",直接把线程池状态置为STOP。不管工作线程是否在执行,全部一一中断,也不等待队列中未执行的任务,把它们返回给客户线程由客户线程自行处置。

java.util.concurrent.ThreadPoolExecutor#shutdownNow流程图

ThreadPoolExecutor源码分析

源码分析主要从重要成员变量和核心方法两个层面来讲解。

关键属性

public class ThreadPoolExecutor extends AbstractExecutorService {
  //控制变量-存放状态和线程数
	private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
  //线程池阻塞队列
  private final BlockingQueue<Runnable> workQueue;
  //工作线程集合,存放(活跃的)工作线程,只有持有mainLock才能访问这个集合。
  private final HashSet<Worker> workers = new HashSet<>();
  //全局锁
  private final ReentrantLock mainLock = new ReentrantLock();
  //awaitTermination方法使用的等待条件变量
	private final Condition termination = mainLock.newCondition();
  //记录峰值线程数,只有持有全局锁才能访问
  private int largestPoolSize;
  //记录完成任务数,只有持有全局锁才能访问
  private long completedTaskCount;
  //线程工厂
  private volatile ThreadFactory threadFactory;
  //拒绝执行处理器
  private volatile RejectedExecutionHandler handler;
  //任务超时时间
  private volatile long keepAliveTime;
  //是否允许coreSize超时,默认不允许
  private volatile boolean allowCoreThreadTimeOut;
  //线程池容量
  private volatile int maximumPoolSize;
  
  private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
			//工作线程
      final Thread thread;
      //初始要执行的任务,有可能是null
    	Runnable firstTask;
      //每一个线程完成的任务数量
    	volatile long completedTasks;

  }
  
  /**
     * 创建线程池
     *
     * @param corePoolSize 核心线程数量
     * @param maximumPoolSize 最大线程容量
     * @param keepAliveTime 超过CoreSize的工作线程保持的时间,超时即终止
     * @param unit keepAliveTime的单位
     * @param workQueue 阻塞工作队列
     * @param threadFactory 线程工厂,默认Executors.defaultThreadFactory()
     * @param handler 拒绝执行处理回调
     * @throws IllegalArgumentException if one of the following holds:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@code maximumPoolSize < corePoolSize}
     * @throws NullPointerException if {@code workQueue}
     *         or {@code threadFactory} or {@code handler} is null
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
}

状态控制

在ThreadPoolExecutor中是通过位运算来处理状态的。位运算的基础是理解原码反码补码,用int ctl = 1来举例

  • 原码: 0000 0000 0000 0000 0000 0000 0000 0001,十进制是1
  • 反码:1111 1111 1111 1111 1111 1111 1111 1110,十进制是-2
  • 补码:1111 1111 1111 1111 1111 1111 1111 1111, 十进制是-1
		//ctl的初始值是1110 0000 0000 0000 0000 0000 0000 0000,即状态为RUNNING,worker数量为0
		private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));  	

		//Java中Integer是32位,所以COUNT_BITS等于29
		private static final int COUNT_BITS = Integer.SIZE - 3;
    //0001 1111 1111 1111 1111 1111 1111 1111
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;    

		//-1左移29位 1110 0000 0000 0000 0000 0000 0000 0000 高3位 111代表运行中
		private static final int RUNNING    = -1 << COUNT_BITS;
    //0 左移 29位  高三位000代表SHUTDOWN
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    //1 左移 29位  高三位001代表STOP
    private static final int STOP       =  1 << COUNT_BITS;
    //2 左移 29位  高三位010代表TIDYING
    private static final int TIDYING    =  2 << COUNT_BITS;
    //3 左移 29位  高三位011代表TERMINATED
    private static final int TERMINATED =  3 << COUNT_BITS;

    //获取线程池状态,CAPACITY取反是 1110 0000 0000 0000 0000 0000 0000 0000
    //c & ~CAPACITY ,c代表ctl值,因为低29位都是0,所以与操作得出的值只与高三位相关
    //这个算法证明:ctl高三位决定了线程池的状态
		private static int runStateOf(int c)     { return c & ~CAPACITY; }
    //获取worder数量,低29位于ctl与操作
    private static int workerCountOf(int c)  { return c & CAPACITY; }

    private static int ctlOf(int rs, int wc) { return rs | wc; }

工作线程为0的前提下,小结下线程池的运行状态:

状态名称 位图 十进制值 描述
RUNNING 111-00000000000000000000000000000 -536870912 运行中状态,可以接收新的任务和执行任务队列中的任务
SHUTDOWN 000-00000000000000000000000000000 0 shutdown状态,不再接收新的任务,但是会执行任务队列中的任务
STOP 001-00000000000000000000000000000 536870912 停止状态,不再接收新的任务,也不会执行任务队列中的任务,中断所有执行中的任务
TIDYING 010-00000000000000000000000000000 1073741824 整理中状态,所有任务已经终结,工作线程数为0,过渡到此状态的工作线程会调用钩子方法terminated()
TERMINATED 011-00000000000000000000000000000 1610612736 终结状态,钩子方法terminated()执行完毕

这里有一个比较特殊的技巧,由于运行状态值存放在高3位,所以可以直接通过十进制值(甚至可以忽略低29位,直接用ctl进行比较,或者使用ctl和线程池状态常量进行比较)来比较和判断线程池的状态:

工作线程数为0的前提下:RUNNING(-536870912) < SHUTDOWN(0) < STOP(536870912) < TIDYING(1073741824) < TERMINATED(1610612736)

// ctl和状态常量比较,判断是否小于
private static boolean runStateLessThan(int c, int s) {
    return c < s;
}

// ctl和状态常量比较,判断是否小于或等于
private static boolean runStateAtLeast(int c, int s) {
    return c >= s;
}

// ctl和状态常量SHUTDOWN比较,判断是否处于RUNNING状态
private static boolean isRunning(int c) {
    return c < SHUTDOWN;
}

最后是线程池状态的跃迁图:

线程池状态跃迁图

核心方法总体概括

execute方法源码分析

   /**
     * 异步执行给定的task,即可能用新的线程执行任务,也可以用线程池中已存在的线程执行任务
     * 如果线程池已经shutdown,那么会拒绝执行任务并抛出异常,由RejectedExecutionHandler处理
     *
     * @param command 要执行的任务
     * @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. 如果工作线程小于corePoolSize,ThreadPoolExecutor会把command当成firstTask,
         * 交给新创建的线程。addWorker函数会校验线程池状态和线程池数量.
         *
         * 2. 如果任务能够入队, 我们还是要进行double-check是否新加线程还是用线程池中的工作线程,因为线程可能在最后一次check的时候死掉. 
         *
         * 3. 如果任务没有进入队列,就尝试创建一个新的线程。如果创建失败了,则线程池shutdown或者饱和,拒绝了这次请求
         */
        int c = ctl.get();
        //如果工作线程数量小于corePoolSize则创建一个core worker
        if (workerCountOf(c) < corePoolSize) {
            //默认把command当做firstTask
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        //说明创建新的核心线程失败,也就是当前工作线程数大于等于corePoolSize
        //判定线程池是否是RUNNING,工作队列添加command
        if (isRunning(c) && workQueue.offer(command)) {
            //再次确认状态,如果线程池状态不是RUNNING并且移除成功则拒绝掉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);
    }

这里简单的分析下流程:

  • 如果工作线程数量小于核心线程数量workerCountOf(c) < corePoolSize,则直接创建核心线程执行任务
  • 如果工作线程数量大于等于核心线程数量,判断线程池状态,并把任务放入到阻塞队列中等待调度。这里会进行二次检查线程池的状态,如果当前工作线程数量为0,则创建一个非核心线程并传入的任务对象为null。
  • 如果添加任务队列失败,则说明阻塞队列已满,尝试创建非核心线程
  • 如果创建非核心线程失败,则证明线程池已经饱和,调用拒绝策略执行任务

这里是一个疑惑点:为什么需要二次检查线程池的运行状态,当前工作线程数量为0,尝试创建一个非核心线程并且传入的任务对象为null?这个可以看API注释:

如果一个任务成功加入任务队列,我们依然需要二次检查是否需要添加一个工作线程(因为所有存活的工作线程有可能在最后一次检查之后已经终结)或者执行当前方法的时候线程池是否已经shutdown了。所以我们需要二次检查线程池的状态,必须时把任务从任务队列中移除或者在没有可用的工作线程的前提下新建一个工作线程。

addWorker方法源码分析

//检验线程池状态和线程池数量边界,通过校验则新增一个线程到线程池中
//firstTask是新创建的worker第一次要执行的。
//当线程数量少于corePoolSize线程或队列已满(这种情况下需要绕过队列),firstTask不为空
private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            //获取线程池状态
            int rs = runStateOf(c);
            //这个判断是线程池拒绝任务的逻辑,比较绕,包含如下两个逻辑
            //1、线程池状态不再是RUNNING则拒绝创建worker
            //2、线程池状态如果是SHUTDOWN并且firstTask为Null且任务队列中有其他任务则拒绝新的任务
            //其实这个判定的具体业务时:在线程池状态为STOP,TIDYING,TERMINATED状态下,不会再接受新的任务。如果状态是SHUTDOWN,firstTask为空并且workQueue不为空,则可以创建新的工作线程。当队列为空了,就不能继续提交任务了。
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;
            //自旋检测状态
            for (;;) {
                //获取worker的数量
                int wc = workerCountOf(c);
                //判定数量是否大于边界,大于的话则拒绝新建线程
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                //CAS增加工作线程数量
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                //如果CAS失败,则需要再次读取ctl
                c = ctl.get();  // Re-read ctl
                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 {
            //创建工作线程
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                //获取全局锁
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    //获取全局锁之后再次校验避免线程池shutdown
                    int rs = runStateOf(ctl.get());
                    //线程池状态是RUNNING或者状态是SHUTDOWN但是core已满且队列未满
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        //把线程添加进workers容器中
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                //如果worker添加完毕,则需要启动线程
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

通过addWorker方法,得出一个很重要的结论

线程池状态 > SHUTDOWN时(即为STOP,TIDYING,TERMINATED这三种),线程池会拒绝所有的新提交任务。

线程池状态为SHUTDOWN的时候,并不会拒绝全部的任务,只会拒绝firstTask不为空,或者firstTask为空并且workQueue为空。其中firstTask是一个关键点,在工作线程数量小于coreSize或者队列满并且线程数量没有达到maxSize时,firstTask不为空。

所以基于以上分析,当调用threadPool.shutdown()之后,核心线程是提交不进来的,队列满之后的临时线程也提交不进来,只有队列不为空的情况下才能提交进来,直到队列中的任务被全部处理完毕,所有线程就都提交不进来了。

工作线程内部类Worker源码分析

可以看到Worker实现了AQS,实现AQS的目的也是为了保证工作线程内部的线程安全和shutdown时判定工作线程是否在工作中。

private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        final Thread thread;
        Runnable firstTask;
        volatile long completedTasks;

        /**
         * 默认构造函数
         */
        Worker(Runnable firstTask) {
            //设置state初始值为-1,阻止中断工作线程
            setState(-1);
            this.firstTask = firstTask;
            //线程是通过线程工厂创建的.
            this.thread = getThreadFactory().newThread(this);
        }

        public void run() {
            //下文重点讲解
            runWorker(this);
        }

        // Lock methods
        //
        // The value 0 represents the unlocked state.
        // The value 1 represents the locked state.

        protected boolean isHeldExclusively() {
            return getState() != 0;
        }

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

        protected boolean tryRelease(int unused) {
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        public void lock()        { acquire(1); }
        public boolean tryLock()  { return tryAcquire(1); }
        public void unlock()      { release(1); }
        public boolean isLocked() { return isHeldExclusively(); }

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

runWorker源码分析

//自旋的从队列中获取任务并执行
final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // 允许中断线程
    //突然停止标识位
    boolean completedAbruptly = true;
    try {
        //task不为空或者从队列中获取task,如果队列为空则阻塞while
        while (task != null || (task = getTask()) != null) {
            //
            w.lock();
            // If pool is stopping, ensure thread is interrupted;
            // 如果线程池正在停止(STOPING),请确保线程是中断的。
            // 否则,要确保线程不是中断的
            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;
                //递增completedTasks,由于worker.lock,所以这个递增是安全的
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        //
        processWorkerExit(w, completedAbruptly);
    }
}

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
      decrementWorkerCount();

    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
      completedTaskCount += w.completedTasks;
      workers.remove(w);
    } finally {
      mainLock.unlock();
    }

    tryTerminate();

    int c = ctl.get();
    if (runStateLessThan(c, STOP)) {
      if (!completedAbruptly) {
        int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
        if (min == 0 && ! workQueue.isEmpty())
          min = 1;
        if (workerCountOf(c) >= min)
          return; // replacement not needed
      }
      addWorker(null, false);
    }
}

runWorker执行过程

getTask方法源码分析

//getTask返回NULL代表工作线程将要终止
private Runnable getTask() {
  //记录上次一poll时是否超时
  boolean timedOut = false; // Did the last poll() time out?

  for (;;) {
    int c = ctl.get();
    //获取线程池状态
    int rs = runStateOf(c);

    //线程池状态为SHUTDOWN时,如果工作队列为空,则decrementWorkerCount,返回NULL
    //线程池状态大于SHUTDOWN时,decrementWorkerCount,返回NULL
    if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
      //原子的减少线程池数量,ctl低29位
      decrementWorkerCount();
      return null;
    }
    // 获取工作线程数量
    // 到这里说明线程池还处于RUNNING状态
    int wc = workerCountOf(c);

    // 允许core线程超时或者线程数量大于coreSize
    boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
    // 
    if ((wc > maximumPoolSize || (timed && timedOut))
        && (wc > 1 || workQueue.isEmpty())) {
      if (compareAndDecrementWorkerCount(c))
        return null;
      continue;
    }

    try {
      Runnable r = timed ?
        workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
      workQueue.take();
      // 只有非null的时候才返回,null的情况下会进入下一轮循环
      if (r != null)
        return r;
      timedOut = true;
    } catch (InterruptedException retry) {
      timedOut = false;
    }
  }
}

这个方法中,有两处十分庞大的if逻辑,对于第一处if可能导致工作线程数减去1直接返回null的场景有:

  1. 线程池状态为SHUTDOWN,一般是调用了shutdown()方法,并且任务队列为空。
  2. 线程池状态为STOP

对于第二处if,逻辑有点复杂,先拆解一下:

复制// 工作线程总数大于maximumPoolSize,说明了通过setMaximumPoolSize()方法减少了线程池容量
boolean b1 = wc > maximumPoolSize;
// 允许线程超时同时上一轮通过poll()方法从任务队列中拉取任务为null
boolean b2 = timed && timedOut;
// 工作线程总数大于1
boolean b3 = wc > 1;
// 任务队列为空
boolean b4 = workQueue.isEmpty();
boolean r = (b1 || b2) && (b3 || b4);
if (r) {
    if (compareAndDecrementWorkerCount(c)){
        return null;
    }else{
        continue;
    }
}

这段逻辑大多数情况下是针对非核心线程。在execute()方法中,当线程池总数已经超过了corePoolSize并且还小于maximumPoolSize时,当任务队列已经满了的时候,会通过addWorker(task,false)添加非核心线程。而这里的逻辑恰好类似于addWorker(task,false)的反向操作,用于减少非核心线程,使得工作线程总数趋向于corePoolSize。如果对于非核心线程,上一轮循环获取任务对象为null,这一轮循环很容易满足timed && timedOut为true,这个时候getTask()返回null会导致Worker#runWorker()方法跳出死循环,之后执行processWorkerExit()方法处理后续工作,而该非核心线程对应的Worker则变成“游离对象”,等待被JVM回收。当allowCoreThreadTimeOut设置为true的时候,这里分析的非核心线程的生命周期终结逻辑同时会适用于核心线程。那么可以总结出keepAliveTime的意义:

  • 当允许核心线程超时,也就是allowCoreThreadTimeOut设置为true的时候,此时keepAliveTime表示空闲的工作线程的存活周期。
  • 默认情况下不允许核心线程超时,此时keepAliveTime表示空闲的非核心线程的存活周期。

在一些特定的场景下,配置合理的keepAliveTime能够更好地利用线程池的工作线程资源。

processWorkerExit方法源码分析

processWorkerExit是在工作线程跳出工作队列死循环时,更新线程池数量,线程池容器。

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    //突然结束,是因为线程抛出用户异常,直接使工作线程数减1
    if (completedAbruptly)
      decrementWorkerCount();
		
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
      //全局锁锁定情况下,线程安全的方式更新完成的任务数量并移除Worker
      completedTaskCount += w.completedTasks;
      workers.remove(w);
    } finally {
      mainLock.unlock();
    }
    //用于根据当前线程池的状态判断是否需要进行线程池terminate处理
    tryTerminate();

    int c = ctl.get();
    //如果状态小于STOP,即RUNNING 或 SHUTDOWN
    if (runStateLessThan(c, STOP)) {
      //并且不是线程抛出用户异常
      if (!completedAbruptly) {
        int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
        //如果允许核心线程超时且队列不为空,则至少保证一个工作线程
        if (min == 0 && ! workQueue.isEmpty())
          min = 1;
        //工作线程大于等于最小值,直接返回不新增非核心线程
        if (workerCountOf(c) >= min)
          return; // replacement not needed
      }
      //工作线程小于最小值,需要新增非核心线程
      addWorker(null, false);
    }
}

tryTerminate方法源码分析

每个工作线程在工作结束之后都会调用tryTerminate方法,总结为:

满足两种情况就会把线程池状态置为TERMINATED 1、状态为SHUTDOWN切队列数量为0,工作线程数量为0,对应shutdown关闭的情况 2、状态为STOP且工作线程数量为0,对应shutdownNow关闭的情况

//满足两种情况就会把线程池状态置为TERMINATED
//1、状态为SHUTDOWN切队列数量为0,工作线程数量为0,对应shutdown关闭的情况
//2、状态为STOP且工作线程数量为0,对应shutdownNow关闭的情况
final void tryTerminate() {
    for (;;) {
      int c = ctl.get();
      //1、线程池状态是RUNNING,不做任何操作直接返回
      //2、线程池状态是TIDYING,不做任何操作直接返回
      //3、线程池状态是SHUTDOWN,且队列不为空不做任何操作直接返回
      if (isRunning(c) ||
          runStateAtLeast(c, TIDYING) ||
          (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
        return;
      // 工作线程数不为0,则中断工作线程集合中的第一个空闲的工作线程
      if (workerCountOf(c) != 0) { // Eligible to terminate
        interruptIdleWorkers(ONLY_ONE);
        return;
      }

      final ReentrantLock mainLock = this.mainLock;
      mainLock.lock();
      try {
        //CAS设置状态为TIDYING状态
        if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
          try {
            //执行terminated方法
            terminated();
          } finally {
            //执行terminated方法完毕之后,设置线程池状态为TERMINATED
            ctl.set(ctlOf(TERMINATED, 0));
            // 唤醒阻塞在termination条件的所有线程,这个变量的await()方法在awaitTermination()中调用
            termination.signalAll();
          }
          return;
        }
      } finally {
        mainLock.unlock();
      }
      // else retry on failed CAS
    }
}

//中断空闲的工作线程,以便它们可以检查终止或配置更改
//如果onlyOne未true,中断最多一个线程。
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();
    }
}

关闭线程池方法源码分析

首先和关闭线程池相关的方法有三个shutdown()shutdownNow()awaitTermination(long timeout, TimeUnit unit)

//已提交的任务还会继续执行,但是新的任务将被拒绝,shutdown不会影响用户的正常使用 
public void shutdown() {
     final ReentrantLock mainLock = this.mainLock;
     mainLock.lock();
     try {
       //校验权限,安全策略 SecurityManager
       checkShutdownAccess();
       //CAS更新状态为SHUTDOWN
       advanceRunState(SHUTDOWN);
       //中断所有空闲的工作线程
       interruptIdleWorkers();
       //钩子方法
       onShutdown(); // hook for ScheduledThreadPoolExecutor
     } finally {
       mainLock.unlock();
     }
     //尝试把线程池状态置为TERMINATED
     tryTerminate();
 }


public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
      //校验权限,安全策略 SecurityManager
      checkShutdownAccess();
      //CAS更新状态为STOP
      advanceRunState(STOP);
      //中断所有空闲的工作线程
      interruptWorkers();
      //清空工作队列,并返回未执行的任务列表
      tasks = drainQueue();
    } finally {
      mainLock.unlock();
    }
    //尝试把线程池状态置为TERMINATED
    tryTerminate();
    //返回任务列表
    return tasks;
}


public boolean awaitTermination(long timeout, TimeUnit unit)
    throws InterruptedException {
    long nanos = unit.toNanos(timeout);
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
      for (;;) {
        //如果发现状态已经变成TERMINATED,则返回true
        if (runStateAtLeast(ctl.get(), TERMINATED))
          return true;
        if (nanos <= 0)
          return false;
        //阻塞直到超时,如果未超时,tryTerminate会signalAll,这时停止阻塞
        nanos = termination.awaitNanos(nanos);
      }
    } finally {
      //释放全局锁
      mainLock.unlock();
    }
}

总结下这三个终止线程池的方法,shutdown()和shutdownNow()是中断所有空闲工作线程,如果线程在执行Runnable#run(),那么这个工作线程是不会被中断的,而是等待下一轮执行getTask()方法的时候,通过线程池状态判断正常终结该工作线程。

awaitTermination很容易被忽略,因为对线程池状态不了解的人,是不会第一时间想到这个方法的作用的。它的好处是调用改方法的线程会阻塞直到线程状态更新为TERMINATED才返回,某些需要感知线程池终止的场景需要调用该方法来终止线程池。

reject方法源码分析

拒绝方法的实现很简单了,就是一个回调函数,具体的拒绝策略需要应用者来实现。

final void reject(Runnable command) {
    handler.rejectedExecution(command, this);
}

场景举例

通过对ThreadPoolExecutor的核心流程和源码的研究,相信大家对它的设计思想和实现原理有了一定的掌握,接下来需要结合实际场景分析下,本文整理了几个景点的场景,分析一下具体的流程,进一步加深理解。

  • 场景1:线程池刚刚创建,第一次submit(Task),然后执行Futrue.get()等待执行结果。

    此刻线程池状态是RUNNING,工作线程数量为零,其数量肯定小于corePoolSize,提交的任务会交给核心线程,且firstTask不为空。工作线程创建好之后,会立即start(),线程开始执行runWorker函数,runWorker中会判断线程的状态不是STOP,这时会执行任务对象的run()(一般情况下是Callable接口的run,Callable接口的run又调用了真正的用户执行逻辑)。在工作线程执行完毕之后,Callable的结果赋值给FutrueTask.outcomeFutrueTask.status == COMPLETINGFutrue.get()解除阻塞,结果返回。

  • 场景2:线程池工作线程数量等于corePoolSize,队列未满,这时submit(Task),然后执行Futrue.get()等待执行结果。

    此刻线程池状态是RUNNING,工作线程数量等于corePoolSize,提交的任务会加入到阻塞队列中。工作线程们“嗷嗷待哺”(阻塞在队列上),这时会有一个工作线程争抢到食物(Task),剩下的流程同场景1。

  • 场景3:线程池工作线程等于corePoolSize,工作队列已满,这时submit(Task),然后执行Futrue.get()等待执行结果。

    此刻线程池状态是RUNNING,由于队列已满,需要加入非核心线程来执行任务,非核心线程数量=maximumPoolSize - corePoolSize,加入新的工作线程且firstTask不为空。剩下的执行流程同场景1。

  • 场景4:执行shutdown()操作。

    在执行shutdown()之后,首先进行安全性检查,并将线程池状态设置为SHUTDOWN中断未执行的线程(通过tryLock可知,在执行的线程是被锁定的),执行中的线程不中断。线程一旦中断,阻塞在队列take()方法或者poll()方法就会抛出InteruptExceptiongetTask()方法内部的自旋重启,因为此刻线程池状态是SHUTDOWN,如果工作队列为空,就直接返回null;如果工作队列不为空,则继续take()出任务交给工作线程执行,知道队列为空,返回null,逐步使工作线程进入Terminated状态。在执行shutdown之后,所有的新提交任务,都会被拒绝,已提交的任务会等待执行完毕。最终线程池状态会变为TERMINATED,如果想获取到线程池终结的事件,需要调用awaitTermination`方法。

  • 场景4:执行shutdownNow()操作。

    在执行shutdownNow()之后,首先进行安全性检查,并将线程池状态设置为STOP中断所有工作线程(不论工作线程是否在工作中),并移除工作队列中全部的任务,返回给用户自行处置。由于工作线程全部中断,阻塞在队列take()方法或者poll()方法就会抛出InteruptExceptiongetTask()方法内部的自旋重启,因为此刻线程池状态是STOP,就直接返回null,跳出死循环,等待工作线程Terminated,最后返回所有的工作队列中未执行的任务。在执行shutdownNow之后,所有的新提交任务,都会被拒绝,未执行的任务会返回给用户自行处理,在执行中的任务虽然工作线程已经被中断,除非任务中处理了中断状态,否则不影响任务执行。

总结

ThreadPoolExecutor作为Java并发编程中最常使用的工作类,研究其实现原理不仅能够熟练运用线程池写出高效的并发代码,避免采坑之外,还能够发掘作者再创作过程中的“点睛之笔”。

ThreadPoolExecutor中,大量运用了位计算来提高性能,线程池的状态和工作线程的数量,都交给一个32位的ctl变量控制,通过把线程抽象为Worker和全局锁mainLock,来保证工作线程执行任务的线程安全,巧妙的运用阻塞队列,来避免线程数量暴增,并通过中断来控制工作线程阻塞与否,线程的终止过程,即可“温和”也可“粗暴”,整个线程池设计精妙,应用者使用如丝般顺滑。

参考文档

JUC线程池ThreadPoolExecutor源码分析