第二十一章 对ThreadPoolExecutor源码的理解及原理探究

前言

最近一段时间都没有空闲阅读和学习Java基础核心知识,主要是最近换了工作,然后忙完工作,又忙结婚,十月份刚刚完成人生大事,以为终于可以歇段时间,结果小bybe又来了,看来没空歇了。既然没空歇了,只有在日常间歇挤出时间学习了。

下面本章将会分析Java的线程池源码,以及一些原理分析,如果有不正确的地方请指正。先看线程池常用的家族体系。
在这里插入图片描述

在常用家族体系结构中,Executor是线程池顶层接口,ExecutorService也是接口。我们常用的线程池实体类是ThreadPoolExecutor,ScheduledThreadPoolExecutor还有Executors这个工具类。Executors提供了很多创建线程池的方法,但其内部都是基于ThreadPoolExecutor来实现的。还有ScheduledThreadPoolExecutor是一种定时线程池,它也是继承于ThreadPoolExecutor,所以对于研究线程池的人来说,ThreadPoolExecutor的研究分析是其重中之重。

ThreadPoolExecutor 序

它像一台庞大的机器,但又不好在描述具体图像,于是我把它比喻为一艘航空母舰。
航空母舰的特点是非常大,组成结构很复杂,运行机制繁琐,都具有平台作用。ThreadPollExecutor就和航空母舰一样,作为一个载体,它可以装载一些战斗机,每个战斗机都可以去执行不同的任务,不同的时候它也对应着很多种状态。
这就是ThreadPoolExecutor这艘航空母舰的大概描述,下面将分为结构组成、工作原理、实现机制来做一个大概的剖析。

一、 结构组成

ThreadPollExecutor的骨架由六大部分构成。

核心线程数、最大线程数、线程存活时间、任务队列、线程工厂、拒绝策略。
在这里插入图片描述
是不是像个机器人? 有模有样了,一个大机器的形状,但是每个部位怎么设计才能满足需要,需要设计各个部位的参数。
那么每个参数有什么意义呢?参数之间又有什么关联呢?

故事背景: 
近年来,索马里海盗猖獗,许多的商货运输轮船都受到劫持,部分轮船还有人员伤亡,为此某部队准备派遣一艘航空母舰前往打击这些海盗。

1. 任务队列
workQueue:存放任务的队列。

所有关于海盗的任务由总部收集信息后,发向航空母舰。航母可以设置一个任务接收队列。BlockingQueue 是一个阻塞队列的顶层接口,其实现有很多种,常见的几种队列:有界队列、无界队列、延迟队列、同步队列等等。

(1)有界队列代表:ArrayBlockingQueue 、 LinkedBlockingQueue
这种队列的特点有一定的长度,ArrayBlockingQueue 底层由数组实现,必须指定大小。LinkedBlockingQueue默认大小为Integer.MAX_VALUE,他们是一个有固定界限的任务队列。

(2)无界队列代表:PriorityBlockingQueue
这种队列的特点是没有界限,没有容量标志。

设计好了任务系统后,下一步就是飞机了。
飞机是航母上的核心,那么看一下飞机怎么生产出来的,即线程池中的线程。

2. 线程工厂
threadFactory: 创造线程的工厂。它是线程池中的一个变量。ThreadFactory也是一个接口,定义了唯一的顶层方法newThread()。
要配置这个线程工厂可以有两种做法:
(1) 采用常用的线程工厂实现类DefaultThreadFactory。
(2) 采用自己定义的实现类,可以更加个性化的满足业务中的需要。

飞机生产工厂有了,但是不可能让工厂无限制的生产,毕竟每架飞机耗费的资金巨大。所以需要根据业务的需要确定生产个数。
由于通常情况下,任务的数量是浮动的,所以生产的个数需要由两个参数来控制,即核心线程数和最大线程数。

3. 核心线程数
corePoolSize: 代表初始核心工作线程的数量。

在平时,航母上只装载了几架飞机,虽然索马里的海盗猖獗,但是好在不多,派出的飞机足以将他们打击。
但是临近过年了,平时不做海盗的平民也来当海盗了,越来越多的海盗出现在了索马里。航母上的飞机一下子忙不过来了。
这时候开始向总部请求支援飞机,但是最多可以支援多少呢,这取决于航母设计时甲板的容纳量。

4. 最大线程数
maximumPoolSize: 代表可以容纳包括核心线程在内的最大工作线程的数量。
触发条件: 核心线程用完,并且任务队列已满。

增加了飞机后,有两个可能性。
(1)战斗力增强后,所有海盗都能够被打击了。
(2)即使甲板的飞机容量已经达到上限了,海盗们还是打击不过来。

按照上述第一个可能性,增派了飞机,海盗都能够被打击了,工作又恢复到平日里的样子。随着过年那段时间一过,平日里海盗又没有那么多了。
本该高兴的事情,舰长开始愁了,后面增派飞机后,每个月都要增加保养、加油、训练,还得让食堂多准备些馒头,这些飞行员真能吃,一口气吃仨个。
所以舰长想了想得让那些待业的飞机回去。

5. 线程存活时间
keepAliveTime:线程空闲后进行回收的时长。
unit: 这个是时间单位。配合keepAliveTime组成一个时间。

这个参数是舰长想出来的一个损招,发出了一个全员通告:任何飞机,如果没有那么多海盗了,待机满XX天后必须返回总部。
这是发给所有飞机的一个共同的通告,但是为了维持日常任务需要,还是会留下核心数量多的飞机,这样食堂的馒头也不用抢了,舰长打着自己的小算盘。
但是既然是发给所有飞机的通告,那么当索马里没有海盗了,核心数量多的飞机需要回去吗?这取决于allowCoreThreadTimeOut这个参数是否为true。

6. 拒绝策略
handler: 线程池满了以后的拒绝策略。
触发条件:线程使用完了,并且任务队列也满了才会触发。所以当在使用无界阻塞队列的情况下,拒绝策略几乎不会被触发。

按照刚刚的第二种可能性,如果所有飞机都派完了,任务队列也装满了,总部不断发出的任务怎么办?

拒绝策略有四种,分别是AbortPolicy、DiscardPolicy、DiscardOldestPolicy、CallerRunsPolicy。它们都实现了RejectedExecutionHandler接口,并实现了各自的rejectedExecution()方法,每个拒绝策略的该方法实现就是其具体的拒绝实现细节。

AbortPolicy:线程池默认的一种拒绝策略。它表示总是抛出异常。
DiscardPolicy:它的rejectedExecution()方法什么也没做,所以会直接导致任务丢弃。
CallerRunsPolicy:这种拒绝策略在线程未关闭的情况下会直接在调用者的线程中执行任务,如果线程被关闭了,如同DiscardPolicy导致任务丢弃。
DiscardOldestPolicy:最后一种拒绝策略它表示,如果线程池未关闭,则会将新任务添加到任务队列尾部,同时会排挤掉队列首部的任务。如果线程池已关闭,则如同DiscardPolicy。

二、 工作原理

根据上述参数的特性,在此将线程池的运行情况分为以下几个部分,并引入例子,方便理解:

ThreadPoolExecutor executor = new ThreadPoolExecutor(
										   (corePoolSize)5,
											(maximumPoolSize) 20,
											(keepAliveTime) 30,
											(unit) TimeUnit.SECONDS,
											(workQueue) new LinkedBlockingDeque<>(),
											(threadFactory) Executors.defaultThreadFactory(),)

(1)刚刚开机,没有任务
刚刚开机,即刚刚创建好线程池的时候,此时线程池内没有线程被创建。只是一个空池子,此时处于待命状态。
在这里插入图片描述

有些人认为线程池创好以后,核心线程池也会立即被创建出来了。其实不是,核心线程什么时候被创建呢,下一步会具体分析。
(2)同时接收到的任务数量 <= 核心线程数[5]。
当第一个任务传来时,由于此前线程池是空的,所以会利用线程工厂立即创建一个线来执行此任务。

思考? 按上面的理论,当第二个任务传来时,也会立即创建第二个线程吗?答:非也!

如果第二个任务来临时,第一个线程还在执行,线程池则会接着创建第二个线程,如果第一个线程是空闲的,则还是利用第一个线程来执行该任务。
后面的线程同理,在满足本条件范围内,都是能复用则复用,不能复用时才创建新的核心线程。

如果任务数量继续增加时,请看下一步分析。

(3)同时接收到的任务数量 > 核心线程数时,分a、b、c、d四种情况。
a .任务数量 <= (核心线程数[5] + 任务队列长度[2^31 - 1])
此情况下,由于核心线程都在运行中,后面的任务则会被添加到任务阻塞队列。
如果有一些核心线程运行完任务了,会继续去队列中取出任务,所以队列中取任务的操作可能是一个多线程并发行为,为了保证线程安全性,采用阻塞队列而不是传统队列。

b. 任务数量 > (核心线程数[5] + 任务队列长度[2^31 - 1])
随着任务突然的爆发性增长,队列已经被装满了,无法满足任务增长的需求。
此时,线程池会创建临时线程,以满足任务需要。

思考:此时的临时线程执行的任务是队列中poll出来的任务,还是任务队列装满后多余出来的任务?

这个问题可以在方法execute(Runnable command)中找到,此时临时线程是执行这个无法被添加到队列去的任务,属于多余出来的任务。而只要队列中又被消费了一个任务,后续任务又会优先添加到队列中去。

c. 任务数量 <= (最大线程数[20] + 任务队列长度[2^31 - 1])
线程池中的线程会按照b的策略,如果需要临时线程,线程池会不断创建出这些临时线程。创建的最大线程个数取决于参数maximumPoolSize。
而一旦发现超过了这个参数。就会发生下面d的情况。

d. 任务数量 > (最大线程数[20] + 任务队列长度[2^31 - 1])
如果此时队列已装满,线程个数已达到最大数量。线程池为该情况提供了四种拒绝策略。调用者可以通过设置拒绝策略和判断拒绝策略来实现业务要求。

(4)线程回收机制
核心线程和临时线程的回收机制是一样的。回收机制有两方面来决定。

  1. 是否进行回收
  2. 多久后进行回收

针对第一个问题,线程池规定临时线程是必须要回收的。而核心线程是根据用户设置的allowCoreThreadTimeOut值来确定是否回收,但是需要注意的是,一旦allowCoreThreadTimeOut设置为True,核心线程就可能被回收,而一旦核心线程数量被回收下降至0时,会引起线程池自动关闭。
针对第二个问题,核心线程和临时线程的回收时长都是一样的,有keepAliveTime + unit来决定的。

三、 实现机制

线程池看似复杂,其实内部很简单。只要弄清楚了上述线程池运行原理,带着原理去理解源代码,犹如轻车熟路。
首先来看一下内部成员变量,我将其分为常规性变量和特殊变量。

变量

常规性变量:

private final BlockingQueue<Runnable> workQueue; 				// 任务队列
private final ReentrantLock mainLock = new ReentrantLock();		// 线程池的主锁
private final Condition termination = mainLock.newCondition();	// 配合线程池主锁的释放器
private final HashSet<Worker> workers = new HashSet<Worker>();	// 线程的存储容器 
private int largestPoolSize;									// 最大线程数量
private long completedTaskCount;								// 线程池以及执行完成的任务个数
private volatile ThreadFactory threadFactory;					// 线程池的线程工厂
private volatile RejectedExecutionHandler handler;				// 拒绝策略
private volatile long keepAliveTime;							// 线程空闲后的回收时间限制
private volatile boolean allowCoreThreadTimeOut; 				// 是否允许核心线程超时后进行回收
private volatile int corePoolSize;								// 线程池的核心线程数量
private volatile int maximumPoolSize;							// 线程池的最大线程数量
private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();// 默认的拒绝策略

特殊变量

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); // 一个原子类的CAS操作
private static final int COUNT_BITS = Integer.SIZE - 3;					// COUNT_BITS 为29,表示int类型二进制中,其中29位用来表示线程池中线程的最大运行数量,剩余3位用来表示线程池状态。
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;			// 线程池中的线程最大容量
private static final int RUNNING    = -1 << COUNT_BITS;		// 运行态,二进制111 00000000000000000000000000000
private static final int SHUTDOWN   =  0 << COUNT_BITS;		// 关闭态,二进制000 00000000000000000000000000000
private static final int STOP       =  1 << COUNT_BITS;		// 停止态,二进制001 00000000000000000000000000000
private static final int TIDYING    =  2 << COUNT_BITS;  	// 整理态,二进制010 00000000000000000000000000000
private static final int TERMINATED =  3 << COUNT_BITS; 	// 结束态,二进制011 00000000000000000000000000000 

关于特殊变量的设计思路,从上面也许已经看出了一些端倪。

  1. 设计者是想用int类型的值,来表示当前线程池的运行状态和线程运行数量两个数值。其中int类型的32位字节中,前3位用来表示状态,后29位用来表示数量。
  2. ctl变量就是这样一个集成的原子类变量,并且从
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    可以看出,线程池创建后,默认是RUNNING状态,线程数量为0。这也解释了上述原理中为什么线程池刚刚创建后,并没有将核心线程数创建出来,而是一个空线程池的原因。
  3. 从状态值的设计来看,RUNNING状态是最小,比它大的都是非RUNNING状态。所有后面源码中有些地方就是通过RUNNING态和非RUNNING态来做判断。

构造器

构造器就是对变量简单的赋值,并且有几个重载。

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

Worker(线程)

这个是线程池的核心,我们理解为工人,集成了Thread 和 Runnable。
所以它可以用获得的线程执行接收到的任务。

final Thread thread;
Runnable firstTask;
volatile long completedTasks;

构造一个Worker是需要一个任务的,所以这个任务是它需要执行的第一个任务。

Worker(Runnable firstTask) {
        setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);		// 利用线程工厂创建。
    }

有了任务,并且也有了线程。那么Worker就可以用线程执行任务了,如何执行呢?

public void run() {
        runWorker(this);
    }
    
官方注释:
主工作程序运行循环。从队列中反复获取任务并执行它们,同时处理一些问题:
1. 我们可以从一个初始任务开始,在这种情况下,我们不需要获得第一个任务。否则,只要池在运行,我们就会从getTask获得任务。如果返回null,则工作程序将由于池状态或配置参数的更改而退出。其他退出是由于抛出外部代码中的异常导致的,在这种情况下complete突然保持,这通常会导致processWorkerExit替换此线程。
2.在运行任何任务之前,会获得锁,以防止任务执行时其他池中断,然后我们确保除非池停止,否则这个线程不会设置它的中断。
3.在每个任务运行之前,都会调用beforeExecute,这可能会抛出一个异常,在这种情况下,我们会导致线程在不处理任务的情况下死亡(用completedsuddenly true中断循环)。
4.假设beforeExecute正常完成,我们运行任务,收集它抛出的任何异常发送到afterExecute。我们分别处理RuntimeException、Error(规范保证会捕获这两者)和任意可抛掷事件。因为我们不能在Runnable.run中重新抛出可抛弃物,所以我们在抛出时将它们包装在错误中(到线程的UncaughtExceptionHandler中)。保守地说,抛出的任何异常都会导致线程死亡。
5.在task.run完成后,我们调用afterExecute,这也会抛出一个异常,这也会导致线程死亡。根据JLS第14.20节,即使task.run抛出,这个异常也会生效。异常机制的最终效果是,在执行后,线程的UncaughtExceptionHandler提供了我们所能提供的关于用户代码遇到的任何问题的同样准确的信息。

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


详细的源码就不分析了吧,后面再来。
这里分析主要的API。

execute方法

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    /*
     * 分3个步骤进行:
     *
     * 1.如果运行的线程少于corePoolSize,则尝试使用给定命令作为其
     * 第一个任务启动一个新线程。对addWorker的调用会自动检查runState和
     * workerCount,从而通过返回false防止在不应该添加线程的情况下添加线程的错误警报。
     *
     * 2.如果任务可以成功排队,那么我们仍然需要再次检查是否应该添加一个线程
     * (因为现有的线程在上次检查后死亡),或者池在进入此方法后关闭。因此,我们会
     * 重新检查状态,如果停止队列,必要时回滚队列;如果没有线程,则启动一个新线程。
     *
     * 3. 如果它失败了,我们知道我们被关闭或饱和,因此拒绝这个任务。
     */
    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    else if (!addWorker(command, false))
        reject(command);
}

2021.09

submit方法

ThreadPoolExecutor的submit方法是继承自父类。

public Future<?> submit(Runnable task) 同理; 

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

从这里可以看出, submit将传入的Runnable或callable转换成了RunnableFuture, 然后交给执行器的任务变成了RunnableFuture。这个方法还是调用的execute()方法来实现。

submit 与 execute的区别

从submit()方法中可以看出,最重要的步骤是将Runnable或callable转换成了RunnableFuture. 而RunnableFuture引用实际上是个new FutureTask对象。追溯newTaskFor(task)方法:

protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
    return new FutureTask<T>(runnable, value);
}

看下图两种方法流程对比:在这里插入图片描述

  1. execute只能传入Runnable的子类,而submit方法支持Runnable和Callable两种。

  2. 线程池对于通过execute提交的任务,执行的是Runnable.run(), 而submit提交的任务执行的是FutureTask.run()。所以execute的任务会抛异常,submit提交的任务不抛异常的原因也在各自的run()方法里。
    下面看一下FutureTask.run()。

    public void run() {
    if (state != NEW ||
        !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                     null, Thread.currentThread()))
        return;
    try {
        Callable<V> c = callable;
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
                result = c.call();		// 这里方法被执行了
                ran = true;
            } catch (Throwable ex) {    // 执行的异常在run方法里被抓住了。
                result = null;
                ran = false;
                setException(ex);   	// 抓住的异常交给了这个方法
            }
            if (ran)
                set(result);
        }
    } finally {
        // runner must be non-null until state is settled to
        // prevent concurrent calls to run()
        runner = null;
        // state must be re-read after nulling runner to prevent
        // leaked interrupts
        int s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
    }
    
    
    protected void setException(Throwable t) {
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        outcome = t  // 异常被交给了FutureTask的返回值 
        UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
        finishCompletion();
    }
    }
    

从上面两个方法中,submit之所以不抛出异常的原因在于在FutureTask.run()中,异常被抓住并且交给了返回值。如果通过FutureTask.get()获取返回值时,将会抛出异常:

public V get() throws InterruptedException, ExecutionException {
    int s = state;
    if (s <= COMPLETING)
        s = awaitDone(false, 0L);
    return report(s);
}

private V report(int s) throws ExecutionException {
    Object x = outcome;
    if (s == NORMAL)
        return (V)x;
    if (s >= CANCELLED)
        throw new CancellationException();
    throw new ExecutionException((Throwable)x);  // 如果返回结果是异常,从这里抛出!
}

线程池的生命周期

看一下线程池的状态定义:

private static final int RUNNING    = -1 << COUNT_BITS;
private static final int SHUTDOWN   =  0 << COUNT_BITS;
private static final int STOP       =  1 << COUNT_BITS;
private static final int TIDYING    =  2 << COUNT_BITS;
private static final int TERMINATED =  3 << COUNT_BITS;

线程池一共定义了五个状态。RUNNING、SHUTDOWN、STOP、 TIDYING 、TERMINATED;
各个状态对应什么时候呢?

  1. 线程池刚刚被创建出来,处于RUNNING状态。在此状态下,线程池可以管理线程、接收和运行任务等等。

  2. 如调用了shutdown()方法,线程池将会进入SHUTDOWN状态。

     public void shutdown() {
     final ReentrantLock mainLock = this.mainLock;
     mainLock.lock();
     try {
         checkShutdownAccess();
         advanceRunState(SHUTDOWN);   // 设置状态SHUTDOWN
         interruptIdleWorkers();	  // 中断闲置线程
         onShutdown(); // hook for ScheduledThreadPoolExecutor
     } finally {
         mainLock.unlock();
     }
     tryTerminate();
     }
    

    在shutdown(),除了将状态置位SHUTDOWN,同步释放了没有工作的线程,只留下了正在工作的线程。同时, 在线程池任务提交入口execute()方法中,通过状态判断也阻止了任务提交。直到当前队列中的所有任务执行完成。所以归纳如下:
    1、释放闲置线程
    2、拒绝任务提交,提交的新任务按拒绝策略执行。
    3、等待当前队列中的所有任务执行完毕。

  3. 如果调用了shutdownNow()方法,线程池将会进入STOP状态。在此状态下,会有哪些操作呢?

     public List<Runnable> shutdownNow() {
     	List<Runnable> tasks;
     	final ReentrantLock mainLock = this.mainLock;
     	mainLock.lock();
     	try {
         	checkShutdownAccess();
         	advanceRunState(STOP);	 // 设置状态STOP
         	interruptWorkers();		 // 中断所有线程
         	tasks = drainQueue();	 // 导出当前任务队列的任务
     	} finally {
         	mainLock.unlock();
     	}
     	tryTerminate();				// 结束线程池前的工作(类似finally)
     	return tasks;
     	}
    

其中,该方法会中断所有的线程,不管是否在运行中。这里有兴趣的一点是,从线程的生命周期中知道,当前java没有stop强制停止方法的,只有interrupt方法。线程池是怎么实现中断线程的呢?跟踪方法interruptWorkers()调用链:

private void interruptWorkers() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers)
            w.interruptIfStarted();  // 每个线程调用了interruptIfStarted()
    } finally {
        mainLock.unlock();
    }
}

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

果然,线程池还是调用的interrupt()方法,但是interrupt()只是做了一个中断标记,无法真正中断。那线程池是怎么设计的呢?
回到Worker线程中,每个Worker线程是这样运行的。

public void run() {
        runWorker(this);  	// worker的run()调用了runWorker(this);
}

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 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();									// 如果通过上面的条件,就会执行Runnable.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);
    }
}

从整个流程设计上看,中断标志判断是在执行当前任务之前。如果发现线程被中断了,则退出worker。否则继续执行一个又一个任务。
如果被中断时,线程正在执行Runnable.run()方法,并且Runnable.run()方法中没有当前线程的中断标志判断,线程池是无法强行结束当前任务的,即使调用的是shutdown(),它只能等当前任务执行结束。

从上面的RUNNING、SHUTDOWN、STOP可以知道线程池生命周期的一个大概走向,而TIDYING 、TERMINATED是怎么演变过来的呢?
tryTerminate()
在shutdown() 和 shutdownNow()中,最后都会调用tryTerminate()方法,刚刚说了它类似于一个finally的作用。看看最后做了什么。

final void tryTerminate() {
    for (;;) {										// 在一个死循环里面
        int c = ctl.get();					
        if (isRunning(c) ||						    // 进行了一些条件判断
            runStateAtLeast(c, TIDYING) ||
            (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
            return;
        if (workerCountOf(c) != 0) { // Eligible to terminate
            interruptIdleWorkers(ONLY_ONE);
            return;
        }

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {	// 通过判断的话,线程池状态将会进入TIDYING
                try {
                    terminated();							// 最后调用一个 terminated()方法
                } finally {
                    ctl.set(ctlOf(TERMINATED, 0));
                    termination.signalAll();
                }
                return;
            }
        } finally {
            mainLock.unlock();
        }
        // else retry on failed CAS
    }
}

protected void terminated() { }

在tryTerminate()中,有几个知识点:
1、如果条件允许,则线程池状态转为了TIDYING,然后执行一个terminated()方法,在terminated()方法中没有实体。最后将线程池状态转为TERMINATED。整个线程池周期结束。
在这里插入图片描述
这里介绍一下terminated()方法:
terminated()方法是一个子类可扩展的方法,如果平常业务中想在线程池的最后做一点什么,可以通过继承线程池ThreadPoolExecutor实现新的子类,并重写terminated()方法实现业务内容。
2、 条件是什么?条件什么时候能通过?条件通不过怎么办?仔细看一下:

	if (isRunning(c) || runStateAtLeast(c, TIDYING) ||
            (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
            return;
        if (workerCountOf(c) != 0) { // Eligible to terminate
            interruptIdleWorkers(ONLY_ONE);
            return;
        }

条件1:

	isRunning(c ) || runStateAtLeast(c, TIDYING)    这句的意思是,如果非SHUTDOWN、STOP则拒绝通过;
	(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty())  如果是SHUTDOWN,并且队列里还有任务,则拒绝通过。

条件2:

	workerCountOf(c) != 0)   如果线程没有全部结束,则拒绝通过。

所以整合起来,这个条件就是当SHUTDOWN时,队列里也没有任务了。或者SHUTDOWN、STOP后,线程全部结束了(STOP后有可能还有正在运行的线程),才能进入到TIDYING状态。
最后,
TIDYING状态下,调用 terminated()完成后,最后进入TERMINATED状态。

结尾

线程池里的技术点还有很多很多,比如线程池的执行机制、线程池的并发控制、线程池锁的选用等等,如果能够看懂整个线程池的运行和源码理解,我相信对Java多线程技术和业务提升这一块应该会提升不少。
最后,对线程池的理解大概就是这样,欢迎指正。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值