关于线程池的文章很多,有的充斥着理论和各种专业名词,很容易让人犯晕、搞混概念,降低理解线程池的效率。有的写得很详细很好,但忽略了一些地方。于是我索性抛开这些文章,直接深入源码,重新抽丝剥茧,不过难免犯错走进代码逻辑的死胡同,再翻看别人的好文章才得以走通。现在把总结写出来,尽量用源码(jdk 1.8)说话,用简单的语言说通概念和逻辑,作为别人文章的一种补充。如有错误或不足,欢迎指出。

线程池的好处

线程池其中一个好处是什么?

  • 线程复用,减少系统开销。

众所周知,创建一个线程系统开销大。假设有以下代表任务的类

class MyTask implements Runnable{
    public void run(){
        //...
    };
}

如果要创建10个该任务并行执行,就要创建10个线程。

for(int i=0;i<10;i++){
    Thread t = new Thread(new MyTask());
    t.start();
}

如果用了线程池,只需这样。假设使用具有固定线程的线程池,且只有5个线程,执行10个任务,比上例系统开销少一半,平均一个线程执行2个任务,这就是线程复用。

ExecutorService es = Executors.newFixedThreadPool(5);
for(int i=0;i<10;i++){
    es.execute(new MyTask());
}
es.shutdown();

 

线程池的种类和概念

线程池的种类

通过java.util.concurrent.Executors创建不同类型的线程池:

  • Executors.newCachedThreadPool()

        创建无界线程池,动态地根据任务数和处理能力新增或回收线程。任务统统被放在阻塞队列里排队,每个线程都尝试从阻塞队列里取出任务,有任务就执行它。当队列没有任务时,线程阻塞60秒,60秒后依然没有任务,回收线程。当每个线程都繁忙,队列还有任务在等待,就再创建一个线程执行它。

  • Executors.newFixedThreadPool( int n)

        创建有界的线程池,线程池里只有n个线程,线程处理完一个任务就从阻塞队列里取出其他任务。当所有线程都在忙,其他任务在队列里等待。

  • Executors.newSingleThreadExecutor()

        创建单线程的线程池。

  • Executors.newScheduledThreadPool( int n)

        (因为这个线程池是另一个线程池类,所以不在本文讨论之列,详细可看其他文章。)

从Executors源码可以看到,前三个方法创建的都是一样的线程池类ThreadPoolExecutor,只是参数不一样,本文只说这个类。

看ThreadPoolExecutor的构造方法和其属性,稍后逐个说明:

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

    //...(省略部分参数检查源码)
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

ThreadPoolExecutor的内部类Worker

private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
    //...
}

首先Worker实现了Runnable接口。线程池中创建的线程,都是Worker线程,每个Worker线程通过直接调用任务实例的run方法,以实现对任务的执行。

另外Worker还继承了AbstractQueuedSynchronizer(后面简称AQS),使得Worker获得锁功能。为什么要锁功能?稍后再说,预告一下,就是shutdown方法和shutdownNow方法实现原理的区别。

PS:ReentranLock之所以能实现代码块的并发访问控制,就是因为继承了AQS而实现的(忘了是从JDK哪个版本开始,ReentranLock类抛开对其的依赖,而是通过内部类ReentrantLock.Sync来继承),而不是通过关键字synchronized等方式实现,可见其强大之处。以后有机会,我会去研究一下AQS,有结果就再写一篇文章。

ThreadPoolExecutor的重要属性

  • corePoolSize

        直译为核心线程池大小,使用FixedThreadPool时才用到,用于限制创建Worker线程的数量。例如Executors.newFixedThreadPool(5),corePoolSize就是5,只能创建5个Worker线程。

  • maximumPoolSize

        直译为最大线程池大小,使用CachedThreadPool时才用到,默认值为Integer.MAX_VALUE,也用于限制创建Worker线程的数量,当然MAX_VALUE个线程只是极端情况,毕竟CachedThreadPool是按需创建或销毁线程。当使用FixedThreadPool时,此值等于corePoolSize。

  • keepAliveTime

        Worker线程等待任务的时间,使用CachedThreadPool时此值默认为60秒,表示当阻塞队列没有任务时,线程等待60秒再取任务,还是没有的话,Worker线程被回收。FixedThreadPool该值为0

  • workQueue

        它是一个无界LinkedBlockingQueue阻塞队列,任务在此排队等待被Worker获取和执行。队列为空时,Worker线程对队列的take/poll操作会阻塞进入等待状态。(我觉得命名为taskQueue会不会更好一点,毕竟它放的是任务。)

  • allowCoreThreadTimeOut

        决定Worker线程如何从workQueue取出任务的行为。默认为false,当workQueue没有任务,Worker线程阻塞等待,直到该队列有任务为止。设为true,当workQueue没有任务,Worker线程只阻塞keepAliveTime时间,还是没有任务就返回null。

  • RejectExecutionHandler

        拒绝策略,默认为AbortPolicy

  • ThreadFactory

        创建Worker线程的线程工厂类,默认为Executors.DefaultThreadFactory

  • workers

        Worker实例的集合,是个普通的HashSet。每次对workers的增删操作,有ReentranLock保证并发安全。

  • ctl

        这个参数设计很巧妙,既用于记录Worker的数量,也用于记录线程池状态(生命周期)。

ctl属性

这个属性十分重要,看源码:

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
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表示线程池的各种状态,由类型为AtomInteger的ctl记录当前线程池状态,保证对线程池状态的修改都是原子操作。ctl这个属性设计十分巧妙,我觉得是属于大牛级别的技术,可以借鉴应用到其他项目中,下面开始解释。

ctl有两个作用:

  • 记录线程池状态
  • 记录Worker数量,包括活动的和空闲的(或叫阻塞)。

线程池状态有:

  • RUNNING            线程池可用,可以通过execute方法添加新任务
  • SHUTDOWN        线程池已关闭,无法通过execute方法添加新任务,但已经排队的任务或正在执行的任务不受影响
  • STOP                    线程池已停止,既无法通过execute方法添加新任务,在排队或正在执行的任务也要立即中断
  • TIDYING               线程池已经没有任务存在了,Worker数量也为0,可以终结线程池
  • TERMIATED          线程池已终结

奇怪,一个Integer,取值范围是-2^31 到 2^31-1,仅且只能有一个数值,如何既记录线程池状态,又记录Worker数量呢?二进制的厉害之处就在这里了,看下表列出了各状态的值:

属性 十进制 二进制 十六进制
CAPACITY 536870911 0001 1111 1111 1111 1111 1111 1111 1111 0x1FFFFFFF
RUNNING -536870912 1110 0000 0000 0000 0000 0000 0000 0000 0xE0000000
SHUTDOWN 0 0000 0000 0000 0000 0000 0000 0000 0000 0x00000000
STOP 536870912 0010 0000 0000 0000 0000 0000 0000 0000 0x20000000
TIDYING 1073741824 0100 0000 0000 0000 0000 0000 0000 0000 0x40000000
TERMINATED 1610612736 0110 0000 0000 0000 0000 0000 0000 0000 0x60000000

线程池利用ctl的二进制数据的前3位记录状态,后29位记录Worker数量。

例如在RUNNING状态,有1个Worker时,ctl就是:

1110 0000 0000 0000 0000 0000 0000 0001,十进制即 -536870911。前3位111是RUNNING,后29位就是1的二进制。

RUNNING状态,有2个Worker时,ctl就是:

1110 0000 0000 0000 0000 0000 0000 0010,十进制即 -536870910,后29位是2的二进制

RUNNING状态,Worker最多时,ctl就是:

1111 1111 1111 1111 1111 1111 1111 1111,十进制即 -1,后29位是536870911的二进制

要如何从ctl中取得状态值和Worker数呢,通过“按位与”操作即可,看源码:

private static int runStateOf(int ctl){    //根据ctl的值取得状态
    return ctl & ~CAPACITY;
}
private static int workerCountOf(int ctl){ //根据ctl的值取得Worker数
    return ctl & CAPACITY;
}

又如何在ctl中设置状态值和Worker数呢?通过“按位或”操作即可,看源码:

private static int ctlOf(int rs, int wc) {    // rs是状态,wc是Worker数
    return rs | wc;
}

所以,ctl的初始值表示线程池状态是RUNNING,Worker数为0。

关于“按位或”在现实中的应用,可以参考这篇别人的文章《按位或在多选中的应用》

线程池状态的变化看下图,可以看到是单向的,参照上面的表格,线程池状态值只能变大:

线程池的好处-LMLPHP

线程池工作流程及原理

之前已经提到,不管是Executors.newFixedThreadPool、Executors.newCacherThreadPool还是Executors.newSingleThreadExecutor,创建的对象都是ThreadPoolExecutor,只是构造方法的参数不同。现在以FixedThreadPool为例,分析其源码。

假设该线程池只有2个线程,放入3个任务(假设这3个任务都比较耗时),然后shutdown线程池:

ExecutorService es = Executors.newFixedThreadPool(2);
es.execute(new MyTask("Task_1"));
es.execute(new MyTask("Task_2"));
es.execute(new MyTask("Task_3"));
es.shutdown();

该线程池工作基本流程:

1.  创建线程数为2的有界线程池,初始状态为RUNNING。

2.  线程池处于RUNNING状态时,可以通过execute方法往线程池添加任务。

3.  假设每个任务都比较耗时,前2个任务分别给2个Worker线程执行,其余任务在workQueue里排队。

4.  关闭线程池,无法再通过execute方法添加新任务

        a)  假设调用shutdown方法关闭线程池,虽然不能添加新任务,但正在执行的任务和排队的任务不受影响,此时线程池状态为SHUTDOWN。

        b)  假设调用shutdownNow方法关闭线程池,不但无法添加新任务,而且正在执行的任务和排队的任务都被终止,此时线程池状态为STOP。

5.  当一个Worker线程完成一个任务,就立即从workQueue里取出其他任务。当workQueue已经没有任务,此Worker线程处于空闲状态(阻塞),直到workQueue再有任务时才继续执行。

6.  当线程池处于SHUTDOWN状态时,workQueue没有任务,其中一个刚执行完最后一个任务的Worker线程先调用tryTerminate()尝试终结其他空闲的Worker线程,再终结自己。

7.  当所有Worker线程已经终结,线程池状态设为TIDYING,线程池跟着终结,状态最后变为TERMINATED。

execute方法流程图

这个流程图忽略了部分不那么重要的细节,只展示重要的工作流程和逻辑。稍后的源码分析也分开Main线程和Worker线程来分析,我觉得这样比较好理解多线程下的流程。

线程池的好处-LMLPHP

Main线程,execute方法

线程池就通过这个方法添加任务Task

public void execute(Runnable task){
    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(task, true))
            return;
        c = ctl.get();
    }
    if (isRunning(c) && workQueue.offer(task)){
	    //...
    }
}

exeucute方法很简单,先通过ctl取得Worker数(包括繁忙和空闲的),小于corePoolSize就调用addWorker方法创建Worker实例和Worker线程,执行传入的任务Task。大于corePoolSize就将任务Task放入workQueue排队。如果线程池状态不是RUNNING了,就拒绝任务Task。

Main线程,addWorker方法

addWorker用于创建Worker实例,并负责启动Worker线程。分两部分,前一部分通过两个for循环来检测是否足够条件执行addWorker。

private boolean addWorker(Runnable firstTask, boolean core){
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        //  当初我就在这里犯了低级错误,搞错了逻辑,导致之后对流程的理解进入了死循环。当满足这些条件时,返回fasle,拒绝加入任务Task,条件有:
        //  1.	状态为STOP、DIDYING或TERMINATE
        //  2.	SHUTDOWN时firstTask为null
        //  3.	SHUTDOWN时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;

            //  如果以上拒绝条件都没有,就给ctl的Worker数量+1,并跳出循环,进入addWorker的下一部分
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();
            if (runStateOf(c) != rs)
                continue retry;
        }
    }
    //...
}

addWorker方法的第二部分,忽略了try代码和判断代码

Worker w = new Worker(firstTask);
final Thread t = w.thread;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)){
    workers.add(w);		//workers只是个HashSet,由mainLock保证并发安全
}
mainLock.unlock();
t.start();				//启动Worker线程

创建Worker实例,Worker类继承了AQS实现了Runnable接口,其构造方法也很简单

private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
    Worker(Runnable firstTask) {
        setState(-1);                //设置AQS的state状态,0表示未锁定,1表示锁定
        this.firstTask = firstTask;                        //Worker维护了任务Task
        this.thread = getThreadFactory().newThread(this);  //创建Worker线程
    }
    //...
}

初始化Worker时为什么state为-1而不是0,后面会解释。

addWorker方法成功,返回true,并结束execute方法。

当Main线程执行完三次execute方法添加了三个任务Task后(假设每个任务都比较耗时),线程池状态如下。此时还有2个Worker线程,正在执行任务Task_1和Task_2,Task_3在排队。

线程池的好处-LMLPHP

Main线程,shutdown方法

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(SHUTDOWN);  //设置ctl的状态为SHUTDOWN
        interruptIdleWorkers();     //中断空闲的Worker线程,稍后再详细分析
        onShutdown();               //触发shutdown事件,默认啥都没做
    } finally {
        mainLock.unlock();
    }
    tryTerminate();                 //尝试终结线程池
}

由于目前仍有Worker线程在执行任务,在此条件下shutdown方法也仅仅是将线程池状态设为SHUTDOWN,其他行为稍后分析。

此时,Main线程的步骤已经走完,开始看另外两个Worker线程到底在干嘛。

Worker线程,runWorker方法

先看看runWorker的简单流程图,依然忽略了一些不重要的细节

线程池的好处-LMLPHP

众所周知,当Worker线程调用start方法启动线程,实则调用Worker实例的run方法,看源码,它直接调用ThreadPoolExecutor的runWorker方法

public void run() {
    runWorker(this);		//this是Worker实例
}

省略了部分无关紧要的runWorker代码

final void runWorker(Worker w){
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock();                 // a)
    boolean completedAbruptly = true;

    //从Worker里取出任务Task,如果为null就通过getTask方法从workQueue里取,都是null就结束,让当前Worker进入销毁阶段
    while (task != null || (task = getTask()) != null){
        w.lock();              // b)

        //当线程池处于STOP状态,线程中断自己
        if ((runStateAtLeast(ctl.get(), STOP) ||
                (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
            wt.interrupt();

        task.run();           //直接调用任务Task的run方法,执行任务本身
        task = null;          //重置task引用,目的是执行完一个任务,再执行workQueue里的另一个任务
        w.unlock();           // c)
        completedAbruptly = false;
    }
    //当连getTask也返回null表示没有任何任务,可以销毁当前Worker实例
    processWorkerExit(w, completedAbruptly);
}

上面的代码注释不难理解,然而a)、b)和c)步骤是什么鬼呢?有什么用呢?这涉及到Worker线程的中断机制:

a)      通过unlock方法给Worker实例解锁(AQS的state设为1),此时Worker线程还没开始访问任务Task,表示Worker线程可以被其他线程中断

b)      在while代码块里,表示任务Task已经准备就绪,通过lock方法给Worker实例加锁,表示Worker线程开始执行任务,不能被其他Worker线程中断,只能在线程池处于STOP状态时自己中断自己(回顾之前STOP的描述),或被Main线程强制中断。

c)      任务Task已经顺利完成,解锁Worker实例(AQS的state设为0)。

也就是说,当Worker线程在执行任务时,Worker处于lock状态。当完成任务或任务没有就绪,Worker处于unlock状态。

为什么其他Worker线程要取得别的Worker的锁时才能中断它的线程。Main线程则不要取得锁就可以中断别的Worker线程,个中实现原理在后面介绍interruptIdleWorkers方法中会说到。

Worker线程,getTask方法

假设此时任务Task_2已经完成,Woker#2实例就访问workQueue,发现有任务Task_3,就取出并执行。

线程池的好处-LMLPHP

如何从workQueue取出任务Task_3的呢,看getTask方法源码。

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

        //  当满足这些条件时,终止getTask方法,返回null,并将ctl的Worker数量减一。表示当前Worker已经没有任务可以执行,进入销毁步骤
        //  1.	线程池是STOP、TIDYING或TERMINATE时
        //  2.	SHUTDOWN时,workQueue为空
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }
        int wc = workerCountOf(c);

        //使用FixedThreadPool且allowCoreThreadPool为fasle,此为false
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        //如果Worker数超过线程池极限,当然不能再增加活动的Worker。或者poll超时,当前Worker自然也要终结,返回null进入销毁步骤
        if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
                continue;
        }

        //从阻塞队列取出任务Task,如果allowCoreThreadPool为true就用poll方法,否则take方法
        Runnable r = timed ?
                       workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                       workQueue.take();
       if (r != null)
           return r;
       timedOut = true;		//记录poll超时
    }
}

阻塞队列的poll方法和take方法有什么区别呢?

  • poll方法从阻塞队列中取出对象,如果队列为空,则当前线程阻塞keepAliveTime时间再尝试取出,还是没有就返回null,记录超时状态,在重新进入for循环时才试图终结Worker。
  • take方法是,如果队列为空,当前线程则一直阻塞,直到队列有对象为止,返回该对象。

所以当使用FixedThreadPool,都是take方法,Worker线程有可能在这里出现阻塞。使用CachedThreadPool且allowCoreThreadTimeOut为true,则是poll方法。

现在假设这么一个情况,Worker#2线程在getTask方法第一段代码workQueue.isEmpty()时,Task_3还是在的。但中途线程调度器切换到Worker#1线程,并处理完之前的任务,并workQueue.take()取出了Task_3任务执行。线程调度器切换回Worker#2线程,当来到workQueue.take()时,阻塞队列已经没有对象,Worker#2线程阻塞,进入空闲状态。

线程池的好处-LMLPHP

Worker#1线程,runWorker方法

现在Worker#2线程处于阻塞状态,只剩Worker#1线程在运行了。

while (task != null || (task = getTask()) != null){
    w.lock();
    task.run();			//执行任务Task_3
    task = null;
    w.unlock();
}
processWorkerExit(w, completedAbruptly);

当执行完任务Task_3之后,再次调用getTask方法,workQueue为空,返回null,结束while循环,调用processWorkerExit方法,Worker#1开始销毁步骤。

要清楚一点,此时Worker#2实例是没有锁的,没有锁的,没有锁的。因为lock方法在getTask方法之后,而Worker#2在getTask方法中处于阻塞状态,所以Worker#2实例是处于unlock状态,这十分重要,后面会解释。

Worker#1线程,processWorkerExit方法

当Worker没有任务可以处理,进入自销毁阶段。

省略了部分不重要的代码段

private void processWorkerExit(Worker w, boolean completedAbruptly){
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        completedTaskCount += w.completedTasks;    //线程池总的执行任务数
        workers.remove(w);                         //从workers删除一个Worker实例
    } finally {
        mainLock.unlock();
    }
    tryTerminate();

    //...
    addWorker(null, false);
}

最后的addWorker(null)是什么意思呢,就是在RUNNING或SHUTDOWN且workQueue非空时,再创建一个Worker处理workQueue里余下的任务。通常CacherThreadPool才有这一步,意味着所有Worker都繁忙,刚好在processWorkerExit方法执行中又有新任务加入,线程池动态新创建一个Worker进行处理。

如果是FixedThreadPool,addWorker(null)返回false,不做任何事。

Worker#1线程,tryTerminate方法

尝试终结线程池,将状态设为TIDYING,最后设为TERMINATED

简单流程图如下

线程池的好处-LMLPHP

终止tryTerminate方法的条件有:

  1. 线程池状态是RUNNING时
  2. 线程池状态是SHUTDOWN时workerQueue非空
  3. 线程池状态已经是TIDYING或TERMINATE时
  4. ctl的Worker数不为0

ctl的Worker数不是0,意味着还有Worker线程在执行任务,或有Worker线程空闲(阻塞),就调用interruptIdleWorkers(ONLY_ONE)中断一个空闲(阻塞)的Worker线程,并结束。

int c = ctl.get();
if (isRunning(c) || runStateAtLeast(c, TIDYING) ||
        (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
    return;
if (workerCountOf(c) != 0) {
    interruptIdleWorkers(ONLY_ONE);		// 中断一个空闲(阻塞)的Worker线程
    return;
}

worker数为0,表示已经没有任何任务,也没有任何Worker,可以将线程池状态设为TIDYING,然后再设为TERMINATED。

if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
    try {
        terminated();						//空方法
    } finally {
        ctl.set(ctlOf(TERMINATED, 0));
        termination.signalAll();			//唤醒其他线程
    }
    return;
}

Worker#1线程,interruptIdleWorkers方法

负责中断一个空闲的Worker线程。分析这个方法之前,先看看shutdown方法和shutdownNow方法有什么不一样。

shutdown方法的代码片段:

checkShutdownAccess();
advanceRunState(SHUTDOWN);
interruptIdleWorkers();    //看这里
onShutdown();

shutdownNow方法的代码片段:

List<Runnable> tasks;
checkShutdownAccess();
advanceRunState(STOP);
interruptWorkers();    //看这里
tasks = drainQueue();

区别就是:

  • shutdown方法调用interruptIdleWorkers方法
  • shutdownNow方法调用interruptWorkers方法

再看看interruptIdleWorkers方法和interruptWorkers方法有什么不一样

interruptIdleWorkers方法的代码片段:

for (Worker w : workers) {
    Thread t = w.thread;
    if (!t.isInterrupted() && w.tryLock()) {		//尝试取得Worker的锁
        try {
            t.interrupt();
        } catch (SecurityException ignore) {
        } finally {
            w.unlock();
        }
    }
    if (onlyOne)
        break;
}

interruptWorkers方法的代码片段:

for (Worker w : workers)
    w.interruptIfStarted();

可以看到,interruptIdleWorkers方法在尝试中断其他线程时,会先对Worker#2调用tryLock方法尝试取得Worker#2的锁。我还没有研究AQS的源码,但我已经猜测到,当Worker#1线程尝试取得Worker#2的锁时,如果无法取得(代表Worker#2正在执行任务),则Worker#1线程在此阻塞。如果取得,Worker#1线程就中断Worker#2线程,那么Worker#2线程抛出InterruptedException。

interruptWorkers方法就不一样,它不会尝试取得Worker锁,它不管Worker是否有锁,立即中断该线程。看interruptIfStarted方法的源码:

Thread t;
//AQS的state为0表示未锁定,1表示锁定。Worker实例初始化时state为-1,表示我还没开始工作做贡献实现价值呢,你不要中断我的。
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted())
    t.interrupt();

纵观ThreadPoolExecutor所有代码,只有shutdownNow方法调用interruptWorkers方法。

所以,这就是Worker加锁时(正在执行任务)不能被其他Worker线程中断的原理。而当Main线程强制调用shutdownNow方法时,Worker无论是否有锁,也能被Main线程中断。

至此,Worker#1线程的interruptIdleWorkers方法中断了Worker#2线程,Worker#2线程抛出InterruptedException,(回顾getTask方法的代码)。

Worker#1线程也已完结

完结

最后贴出Executors.newCachedThreadPool()的基本流程,依然省略了不那么重要的步骤。

线程池的好处-LMLPHP

如果想了解详细的流程,可以参考别人的文章:

《ThreadPoolExecutor核心实现原理和源码解析<一>》

《ThreadPoolExecutor核心实现原理和源码解析<二>》

03-17 21:41