Java并发:Executor框架(线程池)

前言

        在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源。在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁,这就是”池化资源”技术产生的原因。

问题

  1. 如何新建线程?
  2. 线程池的线程是如何做到复用的?
  3. 任务如何执行?
  4. 线程池是如何做到高效并发的?
  5. 线程如何销毁?超时机制如何实现?

一、什么是线程池?

        线程池 顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。

        简而言之,在使用线程池后,创建线程变成了从线程池获得空闲线程,关闭线程变成了向池子归还线程, 如图所示:
在这里插入图片描述
        在开发过程中,合理地使用线程池能够带来以下好处:

1)降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
2)提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
3)提供线程的可管理性。线程是稀缺资源,如果无限地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。

        当向线程池提交一个任务之后,线程池是如何处理这个任务的呢?线程池的实现原理,处理流程如图所示:
在这里插入图片描述
        从上图中可以看出,当提交一个新任务到线程池时,线程池的处理流程如下:

1)线程池 判断 核心线程池 里的线程 是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务。如果 核心线程池里的线程都在执行任务,则进入下个流程。

2)线程池 判断 工作队列 是否已经满。如果 工作队列没有满,则将新提交的任务存储在这个工作队列里。如果 工作队列满了,则进入下个流程。

3)线程池 判断 线程池的线程 是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果 已经满了,则交给饱和策略来处理这个任务。

二、Executor 框架

        任务是一组逻辑工作单元,而线程则是使任务异步执行的机制。

        在 JDK 5之前,Java的线程既是工作单元,也是执行机制。

        从 JDK 5开始,把工作单元与执行机制分离开来。工作单元包括 Runnable 和 Callable 接口都是对任务处理逻辑的抽象,这种抽象使得我们无须关心任务的具体处理逻辑:不管是什么样的任务,其处理逻辑总是展现为一个具有统一签名的方法——Runnable.run() 或者 Callable.call()。而执行机制由Executor框架提供,Executor 接口则是对任务的执行进行抽象,该接口使得任务的提交方(生产者)只需要知道它调用 Executor.execute 方法便可以使指定的任务被执行,而无须关系任务的具体执行细节。

        可见,Executor 接口使得任务的提交能够与任务执行的具体细节解耦。

1. Executor 框架的结构

         Executor框架主要由以下 3 大部分组成:

  • 任务包括被执行任务需要实现的接口:Runnable 接口或 Callable 接口
  • 任务的执行包括任务执行机制的核心接口 Executor,以及继承自 Executor 的 ExecutorService 接口
  • 异步计算的结果包括接口 Future 和实现 Future 接口的 FutureTask 类

其框架的核心成员如下图所示:
在这里插入图片描述
        Executor 是一个接口,它是 Executor 框架的基础,它将任务的提交与任务的执行分离开来。它的功能十分有限:首先,它只能为客户端代码执行任务,而无法将任务的处理结果返回给客户端代码;其次,Executor 接口实现类内部往往会维护一些工作线程,当我们不再需要一个 Eexecutor 实例的时候,往往需要主动将该实例内部维护的工作线程停掉以释放相应的资源,而 Executor 接口并没有定义相应的方法来关闭它们。这会导致 JVM 无法结束,因为 JVM 只有在所有(非守护)线程全部终止后才会退出。为了解决执行任务的生命周期问题,Executor 扩展了 ExecutorService 接口,添加了一些用于生命周期管理的方法。

        ExecutorService 是一个比 Executor 使用更广泛的子类接口,它继承自 Executor 接口,提供了生命周期管理的方法,返回 Future 对象,以及可跟踪一个或多个异步任务执行状况返回 Future 的方法。它的生命周期有 3 种状态:运行、关闭和已终止。ExecutorService 在初始创建时处于运行状态,调用 shutdown 方法将执行平缓的关闭过程(不再接受新的任务,同时等待已经提交的任务执行完毕——包括哪些还未开始执行的任务);调用 shutdownNow 方法将执行粗暴的关闭过程(尝试取消所有运行中的任务,并且不再启动队列中尚未开始执行的任务)。

        ThreadPoolExecutor 是线程池的核心实现类,用来执行被提交的任务。

        ScheduledThreadPoolExecutor 是一个实现类,可以在给定的延迟后运行命令,或者定期执行命令。

        Future 接口和实现 Future 接口的 FutureTask 类,代表异步计算的结果。

        Runnable 接口和 Callable 接口的实现类,都可以被 ThreadPoolExecutor 或 ScheduledThreadPoolExecutor 执行。

        Executors 类则扮演着线程池工厂的角色,通过 Executors 可以取得一个拥有特定功能的线程池。

方法适用条件及注意实现
public static ExecutorService newFixedThreadPool(int nThreads)返回固定线程数量的线程池。由于该核心线程池大小等于其最大线程池大小,因此该线程池中的工作线程永远不会超时。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,带有线程空闲时,便处理在任务队列中的任务。必须在不再需要该线程池时主动将其关闭
public static ExecutorService newCachedThreadPool()适合用于执行大量耗时较短且提交比较频繁的任务。如果提交的任务执行耗时较长,那么可能导致线程池中的工作线程无限制地增加,最后导致过多的上下文切换,从而使得整个系统变慢
public static ExecutorService newSingleThreadExecutor()适合用来实现单(多)生产者——单消费者模式。
public static ExecutorService newScheduledThreadPool()

        

2. ThreadPoolExecutor 实现分析

2.1 重要成员变量

在这里插入图片描述

  • AtomicInteger ctl

        ctl 代表了 ThreadPoolExecutor 中的控制状态,它是一个复核类型的成员变量,是一个原子整数,借助高低位包装了两个概念:

(1)workerCount:线程池中当前活动的线程数量,占据 ctl 的低 29位;

        AtomicInteger ctl的定义如下:

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

        它占据 ctl 的低 29位,这样,每当活跃线程数增加或减少时,ctl 直接做相应数目的增减即可,十分方便。而 ThreadPoolExecutor 中COUNT_BITS 就代表了 workerCount 所占位数,定义如下:

private static final int COUNT_BITS = Integer.SIZE - 3;

        在Java中,一个int占据32位,而COUNT_BITS 的结果不言而喻,Integer大小32减去3,就是29;另外,既然 workerCount 代表了线程池中当前活动的线程数量,那么它肯定有个上下限阈值,下限很明显就是0,上限呢?ThreadPoolExecutor中CAPACITY 就代表了 workerCount 的上限,它是 ThreadPoolExecutor 中理论上的最大活跃线程数,其定义如下:

private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

        运算过程为1左移29位,也就是00000000 00000000 00000000 00000001 --> 001 0000 00000000 00000000 00000000,再减去1的话,就是 000 11111 11111111 11111111 11111111,前三位代表线程池运行状态runState,所以这里workerCount的理论最大值就应该是29个1,即536870911;

        既然workerCount作为其中一个概念复合在AtomicInteger ctl中,那么ThreadPoolExecutor理应提供从AtomicInteger ctl中解析出workerCount的方法,如下:

private static int workerCountOf(int c)  { return c & CAPACITY; }

        计算逻辑很简单,传入的c代表的是ctl的值,即高3位为线程池运行状态runState,低29位为线程池中当前活动的线程数量workerCount,将其与CAPACITY进行与操作&,也就是与000 11111 11111111 11111111 11111111进行与操作,c的前三位通过与000进行与操作,无论c前三位为何值,最终都会变成000,也就是舍弃前三位的值,而c的低29位与29个1进行与操作,c的低29位还是会保持原值,这样就从AtomicInteger ctl中解析出了workerCount的值。

    //CAS,无锁并发
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    //表示线程池线程数的bit数
    private static final int COUNT_BITS = Integer.SIZE - 3;
    //最大的线程数量,数量是完全够用了
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    // runState is stored in the high-order bits
    //1110 0000 0000 0000 0000 0000 0000 0000(很耿直的我)
    private static final int RUNNING    = -1 << COUNT_BITS;
    //0000 0000 0000 0000 0000 0000 0000 0000(很耿直的我)
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    //0010 0000 0000 0000 0000 0000 0000 0000(很耿直的我)
    private static final int STOP       =  1 << COUNT_BITS;
    //0100 0000 0000 0000 0000 0000 0000 0000(很耿直的我)
    private static final int TIDYING    =  2 << COUNT_BITS;
    //0110 0000 0000 0000 0000 0000 0000 0000(很耿直的我)
    private static final int TERMINATED =  3 << COUNT_BITS;

    // Packing and unpacking ctl
    //获取线程池的状态
    private static int runStateOf(int c)     { return c & ~CAPACITY; }
    //获取线程的数量
    private static int workerCountOf(int c)  { return c & CAPACITY; }
    //组装状态和数量,成为ctl
    private static int ctlOf(int rs, int wc) { return rs | wc; }

    /*
     * Bit field accessors that don't require unpacking ctl.
     * These depend on the bit layout and on workerCount being never negative.
     * 判断状态c是否比s小,下面会给出状态流转图
     */
    
    private static boolean runStateLessThan(int c, int s) {
        return c < s;
    }
    
    //判断状态c是否不小于状态s
    private static boolean runStateAtLeast(int c, int s) {
        return c >= s;
    }
    //判断线程是否在运行
    private static boolean isRunning(int c) {
        return c < SHUTDOWN;
    }

(2) runState:线程池运行状态,占据 ctl 的高3位,有 RUNNING、SHUNTDOWN、STOP、TIDYING、TERMINATED 五种状态。其流转关系如图:
在这里插入图片描述

  • RUNNING:接受新任务,以及对已添加的任务进行处理。线程池的初始化状态是RUNNING,换句话说,线程池一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0。
private static final int RUNNING    = -1 << COUNT_BITS;

        -1在Java底层是由32个1表示的,左移29位的话,即111 00000 00000000 00000000 00000000,也就是低29位全部为0,高3位全部为1的话,表示RUNNING状态,即-536870912;

  • SHUTDOWN:不接受新任务,能够处理已经添加的任务。调用shutdown()方法时,线程池由RUNNING -> SHUTDOWN。
private static final int SHUTDOWN   =  0 << COUNT_BITS;

        0在Java底层是由32个0表示的,无论左移多少位,还是32个0,即000 00000 00000000 00000000 00000000,也就是低29位全部为0,高3位全部为0的话,表示SHUTDOWN状态,即0;

  • STOP:不接受新任务,不处理已提交的任务,并且会中断正在处理的任务。调用线程池中的shutdownNow()方法时,线程池由(RUNNING or SHUTDOWN) -> STOP。
 private static final int STOP       =  1 << COUNT_BITS;

        1在Java底层是由前面的31个0和1个1组成的,左移29位的话,即001 00000 00000000 00000000 00000000,也就是低29位全部为0,高3位为001的话,表示STOP状态,即536870912;

  • TIDYING:所有的任务已结束,workerCount为0,线程过渡到TIDYING状态,将会执行terminated()钩子方法。当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行任务也为空时,就会由SHUTDOWN -> TIDYING。当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP-> TIDYING。
private static final int TIDYING    =  2 << COUNT_BITS;

         2在Java底层是由前面的30个0和1个10组成的,左移29位的话,即010 00000 00000000 00000000 00000000,也就是低29位全部为0,高3位为010的话,表示TIDYING状态,即1073741824;

  • TERMINATED:线程池线程池彻底停止,线程池处于TERMINATED状态。线程池处于TIDYING状态时,执行完terminated()之后, 就会由TIDYING->TERMINATED。
private static final int TERMINATED =  3 << COUNT_BITS;

         3在Java底层是由前面的30个0和1个11组成的,左移29位的话,即011 00000 00000000 00000000 00000000,也就是低29位全部为0,高3位为011的话,表示TERMINATED状态,即1610612736;

         由上面我们可以得知,运行状态的值按照RUNNING–>SHUTDOWN–>STOP–>TIDYING–>TERMINATED顺序值是递增的,这些值之间的数值顺序很重要。随着时间的推移,运行状态单调增加,但是不需要经过每个状态。那么,可能存在的线程池状态的转换是什么呢?如下:

	(1)RUNNING -> SHUTDOWN:调用shutdownNow()方法后,或者线程池实现了finalize方法,在里面调用了shutdown方法,即隐式调用;
	(2)(RUNNING or SHUTDOWN) -> STOP:调用shutdownNow()方法后;
	(3)SHUTDOWN -> TIDYING:线程池和队列均为空时;
	(4)STOP -> TIDYING:线程池为空时;
	(5)TIDYING -> TERMINATED:terminated()钩子方法完成时。

        看下实现获取运行状态的runStateOf()方法,代码如下:

private static int runStateOf(int c)     { return c & ~CAPACITY; }

         ~ 是按位取反的意思,CAPACITY表示的是高位的3个0,和低位的29个1,而~CAPACITY则表示高位的3个1,2低位的9个0,然后再与入参c执行按位与操作,即高3位保持原样,低29位全部设置为0,也就获取了线程池的运行状态runState。

  • BlockingQueue workQueue

        workQueue是用于持有任务并将其转换成工作线程worker的队列。

  • HashSet workers

         workers是包含线程池中所有工作线程worker的集合,仅仅当拥有mainLock锁时才能访问它;

  • long completedTaskCount

         completedTaskCount是已完成任务的计数器,只有在worker线程的终止,仅仅当拥有mainLock锁时才能访问它;

  • volatile ThreadFactory threadFactory

         创建新线程的工厂类;

  • volatile RejectedExecutionHandler handler

         执行过程中shutdown时调用的handler;

  • volatile long keepAliveTime

         空闲线程等待工作的超时时间(纳秒),即空闲线程存活时间;

  • volatile boolean allowCoreThreadTimeOut

        默认值为false,如果为false,core线程在空闲时依然存活;如果为true,则core线程等待工作,直到时间超时至keepAliveTime;

  • volatile int corePoolSize

        核心线程池大小,保持存活的工作线程的最小数目,当小于corePoolSize时,会直接启动新的一个线程来处理任务,而不管线程池中是否有空闲线程;

  • volatile int maximumPoolSize

        线程池最大大小,也就是线程池中线程的最大数量。

2.2 实现原理

        ThreadPoolExecutor 的首要任务当然是提交任务进行处理,那么,在任务提交时,ThreadPoolExecutor的处理流程是怎样的?它是一味的开启线程进行处理,还是有一套完整的逻辑来处理呢?

        我们从入口来看下 ThreadPoolExecutor 执行 execute() 方法的示意图,如图所示:
在这里插入图片描述

        ThreadPoolExecutor 采取上述步骤的总体设计思路,让我们再通过源代码来看看是如何实现的,线程池执行任务的方法如下

java.util.concurrent.ThreadPoolExecutor 代码片段
public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    //第一步,获取ctl
    int c = ctl.get();
    //检查当前线程数是否达到核心线程数的限制,注意线程本身是不区分核心还是非核心,后面会进一步验证
    if (workerCountOf(c) < corePoolSize) {
        //如果核心线程数未达到,会直接添加一个核心线程,也就是说在线程池刚启动预热阶段,
        //提交任务后,会优先启动核心线程处理
        if (addWorker(command, true))
            return;
        //如果添加任务失败,刷新ctl,进入下一步
        c = ctl.get();
    }
    //检查线程池是否是运行状态,然后将任务添加到等待队列,注意offer是不会阻塞的
    if (isRunning(c) && workQueue.offer(command)) {
       //任务成功添加到等待队列,再次刷新ctl
        int recheck = ctl.get();
       //如果线程池不是运行状态,则将刚添加的任务从队列移除并执行拒绝策略
        if (! isRunning(recheck) && remove(command))
            reject(command);
        //判断当前线程数量,如果线程数量为0,则添加一个非核心线程,并且不指定首次执行任务
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
   //添加非核心线程,指定首次执行任务,如果添加失败,执行异常策略
    else if (!addWorker(command, false))
        reject(command);
}

        ThreadPoolExecutor 执行 execute 方法分下面4种情况。

1)如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(注意,执行这一步骤需要获取全局锁)。

2)如果运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue。

3)如果无法将任务加入BlockingQueue(队列已满),则创建新的线程来处理任务(注意,执行这一步骤需要获取全局锁)。

4)如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法。

        这里有2个细节,可以深挖一下:

  • 可以看到execute方法中没有用到重量级锁,ctl虽然可以保证本身变化的原子性,但是不能保证方法内部的代码块的原子性,是否会有并发问题?

  • 上面提到过,addWorker方法可以添加工作线程(核心或者非核心),线程本身没有核心或者非核心的标识,core参数只是用来确定 当前线程数的比较对象是线程池设置的核心线程数还是最大线程数,真实情况是不是这样?

        下面我们再继续看添加线程的核心方法 addWorker,这个方法可以分成两部分,第一部分进行一些前置判断,并使用循环 CAS 结构将线程数量加1。代码如下:

private boolean addWorker(Runnable firstTask, boolean core) {
   //相当于goto,虽然不建议滥用,但这里使用又觉得没一点问题
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);
        //如果线程池的状态到了SHUTDOWN或者之上的状态时候,只有一种情况还需要继续添加线程,
        //那就是线程池已经SHUTDOWN,但是队列中还有任务在排队,而且不接受新任务(所以firstTask必须为null)
       //这里还继续添加线程的初衷是,加快执行等待队列中的任务,尽快让线程池关闭
        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;

        for (;;) {
            int wc = workerCountOf(c);
           //传入的core的参数,唯一用到的地方,如果线程数超过理论最大容量,如果core是true跟最大核心线程数比较,否则跟最大线程数比较
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            //通过CAS自旋,增加线程数+1,增加成功跳出双层循环,继续往下执行
            if (compareAndIncrementWorkerCount(c))
                break retry;
           //检测当前线程状态如果发生了变化,则继续回到retry,重新开始循环
            c = ctl.get();  // Re-read ctl
            if (runStateOf(c) != rs)
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }
    
    ......
}

        第二部分负责新建并启动线程,并将 Worker 添加至 Hashset 中。用了 ReentrantLock 确保线程安全。

	//走到这里,说明我们已经成功的将线程数+1了,但是真正的线程还没有被添加
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
       //添加线程,Worker是继承了AQS,实现了Runnable接口的包装类
        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.
                int rs = runStateOf(ctl.get());
                //检查线程状态,还是跟之前一样,只有当线程池处于RUNNING,或者处于SHUTDOWN并且firstTask==null的时候,这时候创建Worker来加速处理队列中的任务
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                   //线程只能被start一次
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                  //workers是一个HashSet,添加我们新增的Worker
                    workers.add(w);
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
              //启动Worker
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;

        分析完addWorker的源码实现,我们可以回答上面留下的二个疑问:

  • execute 方法虽然没有加锁,但是在addWorker方法内部,加锁了,这样可以保证不会创建超过我们预期的线程数,大师在设计的时候,做到了在最小的范围内加锁,尽量减少锁竞争。

  • 可以看到,core 参数,只是用来判断当前线程数是否超量的时候跟 corePoolSize 还是 maxPoolSize 比较,Worker 本身无核心或者非核心的概念。

         任务如何执行?

        在 addWorker 方法中,线程会被启动。新建线程时,Worker 将自身传入,所以线程启动后会执行 Worker 的 run 方法,这个方法调用了 ThreadPoolExecutor 的 runWorker 方法执行任务,runWorker 中会循环取任务执行,执行逻辑如下:

  • 如果 firstTask 不为空,先执行 firstTask,执行完毕后置空;
  • firstTask 为空后调用 getTask() 从队列中取任务执行;
  • 一直执行到没有任务后,退出 while 循环
  • 调用 processWorkerExit() 方法,将 Worker 移除出 HashSet,此时线程执行完毕,也不再被引用,会自动销毁。

继续看Worker是怎么工作的。

//Worker的run方法调用的是ThreadPoolExecutor的runWorker方法
public void 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 {
        //如果task不是null,或者去队列中取任务,注意这里会阻塞,后面会分析getTask方法
        while (task != null || (task = getTask()) != null) {
           //这个lock在这里是为了如果线程被中断,那么会抛出InterruptedException,而退出循环,结束线程
            w.lock();
            //判断线程是否需要中断
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
               //任务开始执行前的hook方法
                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 {
                   任务开始执行后的hook方法
                    afterExecute(task, thrown);
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
       //Worker退出
        processWorkerExit(w, completedAbruptly);
    }
}

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

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

        // Check if queue empty only if necessary.
       //检查线程池的状态,如果已经是STOP及以上的状态,或者已经SHUTDOWN,队列也是空的时候,直接return null,并将Worker数量-1
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

       // 注意这里的allowCoreThreadTimeOut参数,字面意思是否允许核心线程超时,即如果我们设置为false,那么只有当线程数wc大于corePoolSize的时候才会超时
       //更直接的意思就是,如果设置allowCoreThreadTimeOut为false,那么线程池在达到corePoolSize个工作线程之前,不会让闲置的工作线程退出
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
      //确认超时,将Worker数-1,然后返回
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
            //从队列中取任务,根据timed选择是有时间期限的等待还是无时间期限的等待
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

现在我们可以回答文章一开始提出的问题:

  • 线程池的线程是如何做到复用的。

        线程池中的线程在循环中尝试取任务执行,这一步会被阻塞,如果设置了 allowCoreThreadTimeOut 为 true,则线程池中的所有线程都会在 keepAliveTime 时间超时后还未取到任务而退出。或者线程池已经STOP,那么所有线程都会被中断,然后退出。

  • 线程池是如何做到高效并发的。

        看整个线程池的工作流程,有以下几个需要特别关注的并发点.
        ①: 线程池状态和工作线程数量的变更。这个由一个AtomicInteger变量 ctl来解决原子性问题。
        ②: 向工作Worker 容器 workers 中添加新的 Worker 的时候。这个线程池本身已经加锁了。
        ③: 工作线程Worker 从等待队列中取任务的时候。这个由工作队列本身来保证线程安全,比如 LinkedBlockingQueue 等。

  • 线程如何销毁?超时机制如何实现?

引用

《Java并发编程的艺术》
《Java多线程编程实战指南:核心篇》
《Java并发编程实战》
java并发编程:Executor、Executors、ExecutorService
ThreadPoolExecutor源码分析(一):重要成员变量
彻底理解Java线程池原理篇
https://www.cnblogs.com/rinack/p/9888717.html
彻底弄懂 Java 线程池原理

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值