ThreadPoolExecutor 工作原理

假设corePool=5,队列大小为100,maxnumPoolSize为10

  1. 向线程池新提交一个任务,会根据ThreadFactory创建一个新的线程(包装在worker对象),然后执行任务
  2. 如果工作线程数量超过corePool(这里是5),则会加入到阻塞队列中(这里是有界队列,大小为100)进行排队
  3. 如果队列满(超过100),则会去判断当前工作线程数量小于maxnumPoolSize,小于的话会再去创建线程(worker对象)
  4. 如果corePoolworkQueuemaxnumPoolSize三个都满了,则新提交的任务将执行拒绝策略(默认AbortPolicy)

ThreadPoolExecutor 应用

ThreadPoolExecutor就像AQS一样,是属于比较底层的,也可以说是线程池的基础,我们平时所用到的CachedThreadPool、FixedThreadPool、ScheduledThreadPool、SingleThreadExecutor等都是基于ThreadPoolExecutor,通过对ThreadPoolExecutor的构造函数进行设置实现不同功能的线程池

  • CachedThreadPool:创建一个可缓存线程池,如果线程池长度超过实际需要,可灵活回收空闲线程(60s后回收),若无可用线程,则新建线程
  • FixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
  • ScheduledThreadPool:创建一个定长线程池,支持定时及周期性执行任务
  • SingleThreadExecutor:创建一个单线程的线程池,它会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO、LIFO、优先级)执行。与单个线程的主要区别在于单线程的线程池可以对进行管理后顺序执行
  • SingleThreadScheduledExecutor:同上,在其基础上支持定时及周期性执行任务

ThreadPoolExecutor 源码解析

首先来看下ThreadPoolExecutor的继承关系:

ThreadPoolExecutor 示例

在看源码之前我们先来看一个简单的例子:

    @Testpublic void test() {ExecutorService executorService = new ThreadPoolExecutor(3, // 核心线程数10, // 最大线程数0L, // 当线程数量大于核心线程数时, 空闲线程最大的生存期为keepAliveTimeTimeUnit.MILLISECONDS, // keepAliveTime的时间单位new LinkedBlockingQueue<>(), // 待执行的任务队列new MyThreadFactory("test-pool"),  // 用于创建新线程的工厂类new ThreadPoolExecutor.AbortPolicy() // 由于达到线程边界和队列容量而无法执行时使用的拒绝策略实现类);// 执行10个任务for (int i = 0; i < 10; i++) {executorService.execute(() -> {try {System.out.println("Thread=" + Thread.currentThread().getName());Thread.sleep(1000);} catch (Throwable e) {e.printStackTrace();}});}}

示例中我们构建了一个核心线程数为3最大线程数为10的线程池,接下来我们就通过构造函数、execute方法入手进行源码解析

ThreadPoolExecutor 构造函数

    public ThreadPoolExecutor(int corePoolSize, // 核心线程数int maximumPoolSize, // 最大线程数long keepAliveTime, // 当线程数量大于核心线程数时, 空闲线程最大的生存期为keepAliveTimeTimeUnit unit, // keepAliveTime的时间单位BlockingQueue<Runnable> workQueue, // 待执行的任务队列ThreadFactory threadFactory, // 用于创建新线程的工厂类RejectedExecutionHandler handler) { // 由于达到线程边界和队列容量而无法执行时使用的拒绝策略实现类...}
  • corePoolSize:核心线程数,最少的线程数量
  • maximumPoolSize:在核心线程都在工作的时候,队列中还有任务,则会继续创建线程执行任务,但最终的线程数量不会超过maximumPoolSize
  • keepAliveTime:当线程数量大于核心线程数时, 空闲线程最大的生存期为keepAliveTime
  • unit:与keepAliveTime一一对应,时间的单位
  • workQueue:当核心线程满且最大线程数满,新提交的任务将会加入到队列中去。可选的队列有ArrayBlockingQueue、LinkedBlockingQueue、DelayQueue、DelayedWorkQueue、LinkedBlockingQueue、PriorityBlokingQueue、SynchronousQueue、LinkedTransferQueue
  • handler:由于达到队列容量或线程池被shutdown而无法执行任务时使用的拒绝策略实现类,目前有AbortPolicy、CallerRunsPolicy、DisCardOldestPolicy、DiscardPolicy

一定要熟悉构造函数中每个参数的作用,这是构建一个线程池必要的参数

execute 方法

    public void execute(Runnable command) {if (command == null) {throw new NullPointerException();}/** ctl记录着workCount和runState */int c = ctl.get();/** case1: 如果线程池中的线程数量小于核心线程数,那么创建线程并执行*/if (workerCountOf(c) < corePoolSize) { // workerCountOf(c): 获取当前活动的线程数/*** 在线程池中新建一个新的线程* command:需要执行的Runnable线程* true:新增线程时,【当前活动的线程数】是否 < corePoolSize* false:新增线程时,【当前活动的线程数】是否 < maximumPoolSize*/if (addWorker(command, true)) {// 添加新线程成功,则直接返回。return;}// 添加新线程失败,则重新获取【当前活动的线程数】c = ctl.get();}/** 第二步:如果当前线程池是运行状态 并且 任务添加到队列成功* (即:case2: 如果workCount >= corePoolSize,创建线程往workQueue添加线程任务,等待执行)*/// BlockingQueue<Runnable> workQueue 和 Runnable commandif (isRunning(c) && workQueue.offer(command)) { // 添加command到workQueue队列中。// 重新获取ctlint recheck = ctl.get();// 再次check一下,当前线程池是否是运行状态,如果不是运行时状态,则把刚刚添加到workQueue中的command移除掉,并调用拒绝策略if (!isRunning(recheck) && remove(command)) {reject(command);} else if (workerCountOf(recheck) == 0) { // 如果【当前活动的线程数】为0,则执行addWork方法/*** null:只创建线程,但不去启动* false:添加线程时,根据maximumPoolSize来判断** 如果 (workerCountOf(recheck) > 0, 则直接返回,在队列中的command稍后会出队列并且执行*/addWorker(null, false);}}/*** 第三步:满足以下两种条件之一,进入第三步判断语句*  case1: 线程池不是正在运行状态,即:isRunning(c)==false*  case2: workCount >= corePoolSize 并且 添加workQueue队列失败。即:workQueue.offer(command)==false** 由于第二个参数传的是false,所以如果workCount < maximumPoolSize,则创建执行线程;否则,进入方法体执行reject(command)*/else if (!addWorker(command, false)) {// 执行线程创建失败的拒绝策略reject(command);}}

基本过程:

  1. 根据ctl判断线程池的状态runState和工作线程数workCount
  2. 如果当前工作线程数小于核心线程则在线程池中新建一个新的线程
  3. 否则将任务放到workQueue队列中,放入成功后再次判断工作线程数是否是0,如果是则执行addWork方法
  4. 最后一种情况是当线程池不是运行状态且任务添加到队列失败,则执行拒绝策略

这里有两个需要注意的点:ctl属性构成、addWorker方法,下面会单独讲解

ctl

    // 通过ctl,可以获得当前线程池的运行状态(runStatus)和所包含的线程数量(workerCount)private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

ctl是一个原子int属性,其中有一个初始化的ctlOf方法,我们来看下:

    private static final int COUNT_BITS = Integer.SIZE - 3; // 32 -3 = 29位/*** 计算CAPACITY = (1 << COUNT_BITS) - 1* <p>* 第一步:(1 << COUNT_BITS) = 1 << 29* 00100000000000000000000000000000   (1后面有29个0)* 第二步:00100000000000000000000000000000 - 1* 00011111111111111111111111111111   (29个1)* 结果:* int CAPACITY = 00011111111111111111111111111111 = 536870911(十进制)*/private static final int CAPACITY = (1 << COUNT_BITS) - 1; // 0001左移动29位后,减1,则为从低位到高位,连续29位都位1,防止超出29位 00011111111111111111111111111111// runState is stored in the high-order bitsprivate static final int RUNNING = -1 << COUNT_BITS;   // 1110 0000 0000 0000 0000 0000 0000 0000private static final int SHUTDOWN = 0 << COUNT_BITS;   // 0000 0000 0000 0000 0000 0000 0000 0000private static final int STOP = 1 << COUNT_BITS;       // 0010 0000 0000 0000 0000 0000 0000 0000private static final int TIDYING = 2 << COUNT_BITS;    // 0100 0000 0000 0000 0000 0000 0000 0000private static final int TERMINATED = 3 << COUNT_BITS; // 0110 0000 0000 0000 0000 0000 0000 0000/*** 计算ctl的值* ctl=[3位]线程池状态 + [29位]线程池中线程数量*/private static int ctlOf(int rs, int wc) {return rs | wc; // 按位取或,即:同为0时为0,否则为1。此处可以理解为ctl内容的拼接}

其实就是将线程池状态和工作数量放在一个32bit的int中,前三位表示runState,后29位表示workCount,如图:

由以上代码可知,runState分别有五种状态,作用分别为:

几种状态的关系为:

总的来说,通过将runState和workCount整合成一个属性ctl,可以更好的使用乐观锁的方式控制并发,而不用去同时维护两个属性。通过高3位表示runState,低29位表示workCount(最大可以表示536870911)。

addWorker 方法

    /*** 添加worker来执行任务** @param firstTask 需要执行的Runnable线程* @param core      true:新增线程时,【当前活动的线程数】是否 < corePoolSize*                  false:新增线程时,【当前活动的线程数】是否 < maximumPoolSize* @return true if successful*/private boolean addWorker(Runnable firstTask, boolean core) {retry:/*** 步骤一:试图将workerCount+1*/for (; ; ) {int c = ctl.get();// 获得运行状态runStateint rs = runStateOf(c);/*** 只有如下两种情况可以新增worker,继续执行下去:* case one: rs == RUNNING* case two: rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty()*/if (rs >= SHUTDOWN && // 即:非RUNNING状态(请查看isRunning()方法)。线程池异常,表示不再去接收新的线程任务了,返回false/*** 当线程池是SHUTDOWN状态时,表示不再接收新的任务了,所以:* case1:如果firstTask!=null,表示要添加新任务,则:新增worker失败,返回false。* case2:如果firstTask==null并且workQueue为空,表示队列中的任务已经处理完毕,不需要添加新任务了。则:新增worker失败,返回false*/!(rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty())) {return false;}for (; ; ) {// 获得当前线程池里的线程数int wc = workerCountOf(c);/*** 满足如下任意情况,则新增worker失败,返回false* case1:大于等于最大线程容量,即:int CAPACITY = 00011111111111111111111111111111 = 536870911(十进制)* case2:当core是true时:>= 核心线程数*        当core是false时:>= 最大线程数*/if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) {return false;}// 当前工作线程数加1if (compareAndIncrementWorkerCount(c)) {break retry; // 成功加1,则跳出retry标识的这两层for循环}// 如果线程数加1操作失败,则获取当前最新的线程池运行状态c = ctl.get();// 判断线程池运行状态(rs)是否改变;如果不同,则说明方法处理期间线程池运行状态发生了变化,重新获取最新runStateif (runStateOf(c) != rs) {continue retry; // 跳出内层for循环,继续从第一个for循环执行}}}/*** 步骤二:workerCount成功+1后,创建Worker,加入集合workers中,并启动Worker线程*/boolean workerStarted = false; // 用于判断新的worker实例是否已经开始执行Thread.start()boolean workerAdded = false; // 用于判断新的worker实例是否已经被添加到线程池的workers队列中Worker w = null; // AQS.Workertry {// 创建Worker实例,每个Worker对象都会针对入参firstTask来创建一个线程。w = new Worker(firstTask);// 从Worker中获得新建的线程tfinal Thread t = w.thread;if (t != null) {// 重入锁final ReentrantLock mainLock = this.mainLock;/** ----------lock() 尝试加锁操作!!获得锁后继续执行,没获得则等待直到获得锁为止---------- */mainLock.lock();try {// 获得线程池当前的运行状态runStatusint rs = runStateOf(ctl.get());/*** 满足如下任意条件,即可向线程池中添加线程:* case1:线程池状态为RUNNING。(请查看isRunning()方法)* case2:线程池状态为SHUTDOWN并且firstTask为null。*/if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) {// 因为t是新构建的线程,还没有启动。所以,如果是alive状态,说明已经被启动了,则抛出异常。if (t.isAlive()) {throw new IllegalThreadStateException();}// workers中保存线程池中存在的所有work实例集合workers.add(w);int s = workers.size();if (s > largestPoolSize) { // largestPoolSize用于记录线程池中曾经存在的最大的线程数量largestPoolSize = s;}workerAdded = true;}} finally {/** ----------unlock 解锁操作!!---------- */mainLock.unlock();}if (workerAdded) {/** 开启线程,执行Worker.run() */t.start();workerStarted = true;}}} finally {// 如果没有开启线程if (!workerStarted) {addWorkerFailed(w); // 往线程池中添加worker失败了}}return workerStarted;}

看着代码很多,其实很简单,主要有两步:

  1. 第一步:乐观锁的方式尝试将workerCount+1,过程中会有一些runState、workerCount的判断
  2. 第二步:创建Worker对象,使用重入锁,做一些runState、poolSize判断与将worker加入到workers中

这里有一个关键的地方是Worker对象,它的主要数据结构如下:

    private final class Worker extends AbstractQueuedSynchronizer implements Runnable {final Thread thread;Runnable firstTask;volatile long completedTasks;Worker(Runnable firstTask) {// AQS.state=-1setState(-1); // inhibit interrupts until runWorkerthis.firstTask = firstTask;// 通过ThreadFactory构建线程,生成work的线程this.thread = getThreadFactory().newThread(this);}...
}

从threadFactory中创建了一个线程,其中的runable为this,也就是work.thread.start()将执行worker对象中的run方法:

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

runWorker在下一节会单独说明。

在addWorker方法中,当worker.thread.start()开启线程失败的时候,会进入addWorkerFailed方法,来看下代码:

    /*** 回滚创建的工作线程* 1> 从workers队列中移除worker(如果存在队列中的话)。* 2> workerCount减一。* 3> 重新检查是否终止(termination),以防止这个worker的存在阻止了终止(termination)*/private void addWorkerFailed(Worker w) {final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {if (w != null) {workers.remove(w); // 步骤1:从work队列中移除w}// 步骤2:workerCount减1decrementWorkerCount();// 步骤3:tryTerminate();} finally {mainLock.unlock();}}/*** 如果(state是SHUTDOWN状态,并且线程池和队列都为空)或者(state是STOP状态,并且线程池和队列都为空),则将state状态转换为TERMINATED。* 如果可以终止,但workerCount非零,则中断空闲的worker,以确保关闭信号传播。* 必须在可能终止操作的任何操作之后调用此方法-减少worker计数或在关闭过程中从队列中删除任务。* 该方法是非私有的,以允许从ScheduledThreadPoolExecutor访问。*/final void tryTerminate() {for (; ; ) {int c = ctl.get();/*** 只有runState=STOP 或 SHUTDOWN(且workQueue为空)才能继续往下执行*/if (isRunning(c) || // 判断c是否是RUNNING状态runStateAtLeast(c, TIDYING) || // 判断c是否是TIDYING或者TERMINATED(runStateOf(c) == SHUTDOWN && !workQueue.isEmpty())) { // 判断如果c是否是SHUTDOWN状态并且workQueue不为空return;}// 当前活动的线程数不为0if (workerCountOf(c) != 0) { // Eligible(有资格的) to terminate// 因为ONLY_ONE=true,所以只针对一个闲置线程执行interrupt()interruptIdleWorkers(ONLY_ONE);return;}final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) { // 尝试将当前线程池的运行状态设置为TIDYING,活跃线程数量设置为0try {terminated(); // 空方法} finally {ctl.set(ctlOf(TERMINATED, 0)); // 将当前线程池的运行状态设置为TERMINATED,活跃线程数量设置为0termination.signalAll(); // 唤醒所有线程}return;}} finally {mainLock.unlock();}}}

addWorkerFailed基本过程(整个过程加重入锁):

  1. 从workers队列中移除worker(如果存在队列中的话)
  2. workerCount减1
  3. 重新检查是否终止(termination),以防止这个worker的存在阻止了终止(termination)。如果是termination,则需要将刚才的worker.thread.start()失败的线程进行interrupt

runWorker 方法

在上一节中,执行任务最终是worker.thread.start(),实际调用的是worker对象的runWorker方法执行,来看下代码:

    final void runWorker(Worker w) {Thread wt = Thread.currentThread();Runnable task = w.firstTask;w.firstTask = null;w.unlock(); // allow interruptsboolean completedAbruptly = true;try {while (task != null || (task = getTask()) != null) {w.lock();/*** 如果线程池正在停止,请确保线程被中断;否则,请确保线程不被中断。* 这需要在第二种情况下重新检查以处理shutdownNow竞赛,同时清除中断** 同时满足如下两个条件,则执行wt.interrupt()* 1>线程状态为STOP、TIDYING、TERMINATED 或者 (当前线程被中断(清除中断标记)并且线程状态为STOP、TIDYING、TERMINATED)* 2>当前线程wt没有被标记中断*/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);}}

基本过程:

  1. 如果firstTask为空则获取task任务
  2. 线程状态为STOP、TIDYING、TERMINATED 或者 (当前线程被中断(判断时会清除中断标记)并且线程状态为STOP、TIDYING、TERMINATED)则将线程当前线程标记为中断
  3. 线程没有被标记中断,执行beforeExecute前置方法,然后执行run,然后执行afterExecute后置方法,三个方法为串行执行
  4. worker退出处理工作

获取task任务的方法代码:

    private Runnable getTask() {// 表示上次从阻塞队列中获取任务是否超时boolean timedOut = false;for (; ; ) {int c = ctl.get();int rs = runStateOf(c);// Check if queue empty only if necessary./*** 同时满足如下两点,则线程池中工作线程数减1,并返回null** 1> rs >= SHUTDOWN,表示线程池不是RUNNING状态* 2> rs >= STOP 表示STOP、TIDYING和TERMINATED这三个状态,它们共同点就是【不接收新任务】也【不处理workQueue里的线程任务】or 阻塞队列workQueue为空*/if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {decrementWorkerCount(); // 线程池中工作线程数减1return null;}int wc = workerCountOf(c);// timed用于判断是否需要进行超时控制,当allowCoreThreadTimeOut被设置为ture或者活跃线程数大于核心线程数,则需要进行超时控制// allowCoreThreadTimeOut默认为false,则表明核心线程不允许超时boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;/*** 同时满足以下两种情况,则线程池中工作线程数减1并返回null:* case1:当前活动线程数workCount大于最大线程数,或者需要超时控制(timed=true)并且上次从阻塞队列中获取任务发生了超时(timedOut=true)* case2:如果有效线程数大于1,或者阻塞队列为空。*/if ((wc > maximumPoolSize // 因为在执行该方法的同时被执行了setMaximumPoolSize,导致最大线程数被缩小|| (timed && timedOut))&& (wc > 1 || workQueue.isEmpty())) { //if (compareAndDecrementWorkerCount(c)) { // 线程池中工作线程数减1return null;}// 如果减1失败,则循环重试continue;}try {// 如果需要超时控制,则通过阻塞队列的poll方法进行超时控制,// 否则,直接获取,如果队列为空,task方法会阻塞直到队列不为空Runnable r = timed ?workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : // poll -->若队列为空,返回nullworkQueue.take(); // take --> 若队列为空,发生阻塞,等待元素if (r != null) {return r;}// 如果r=null,表示超时了,则timeOut设置为true,标记为上一次超时状态timedOut = true;} catch (InterruptedException retry) {timedOut = false;}}}

getTask方法中,执行一些判断,不满足要求则返回null,否则直接在workQueue中获取task执行。如果是有超时限制,则从队列poll会一直等待直到某个元素可用

接下来看下processWorkerExit 方法代码:

    private void processWorkerExit(Worker w, boolean completedAbruptly) {// 如果是由于用户异常导致worker死亡if (completedAbruptly) {decrementWorkerCount(); // 将workerCount减1}final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {// 计算任务的完成数量completedTaskCount += w.completedTasks;// 将worker从线程池中移除workers.remove(w);} finally {mainLock.unlock();}tryTerminate();int c = ctl.get();// runState是RUNNING或SHUTDOWNif (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);}}

至此,execute方法所有的执行过程解析完毕。

思考

线程池是如何实现复用的?

在以上的源码分析中其实已经有了体现,在runWorker方法中,有一个while (task != null || (task = getTask()) != null)循环,在Worker第一次工作时,task不为null,当task执行完成后会再去获取task,这个方法就是getTask()
在getTask方法中是一个死循环,关键代码如下:

// 如果需要超时控制,则通过阻塞队列的poll方法进行超时控制,
// 否则,直接获取,如果队列为空,task方法会阻塞直到队列不为空
Runnable r = timed ?workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : // poll -->若队列为空,返回nullworkQueue.take(); // take --> 若队列为空,发生阻塞,等待元素

有两种情况:

  1. 有设置空闲线程超时时间的,在超时时间keepAliveTime到后,会返回一个null,在runWorker方法中就会跳出while循环,线程结束
  2. 没有设置空闲线程超时的,会一直阻塞workQueue.take(),直到队列中有元素的时候继续执行队列中take()出来的任务,此过程不断循环

总的来说,就是在有任务的时候线程执行,没有任务的时候,线程在队列中阻塞,直至有任务可以执行,此过程的不断循环就达到了一个线程复用目的

为什么要用Worker来包装thread和runable?

和上一个线程复用有较大的关系,因为如果是直接使用thread执行runable,则runable结束后,线程就结束了。需要使用Worker来加入线程复用的逻辑,通过Worker对象来调用runable执行,保证线程不会结束以实现线程复用

Executors.newCacheThreadPool()中使用的是SynchronousQueue队列,是不是意味着同时仅有一个任务在运行?

不是!!!
先来看下代码:

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

核心线程数为0,最大线程数为Integer.MAX_VALUE,空闲线程超时时间为60s,使用的是SynchronousQueue队列
在SynchronousQueue中区分公平(Queue)非公平(Stack),默认的构造时非公平的。

基本过程如下:

  1. 在SynchronousQueue中有一个来栈保存REQUEST(可以理解为Consumer,包含线程)和DATA(可以理解为Producer,包含Runable)
  2. 当一个任务进来,会往栈中扔一个DATA,发现栈没有数据,则将任务放到队列失败会返回null,然后创建一个线程来执行任务
  3. 然后又来一个任务,发现栈还是空的,又创建一个线程
  4. 当两个任务执行完成后,会往栈中扔两个REQUEST
  5. 这时如果又来一个任务,发现栈中不为空,则将DATA放入栈,此时DATA和REQUEST会成对出栈,可以直接被执行
  6. 再来一个任务,同上一步一样

按照上面的流程分析,发现真正阻塞的不是DATA,而是QEUST阻塞,在栈中没有DATA的时候,会进行阻塞,直至栈中有DATA

扩展

interrupt()、 interrupted()和isInterrupted()

  • public void interrupt()

其作⽤是中断此线程(此线程不⼀定是当前线程,⽽是指调⽤该⽅法的Thread实例所代表的
线程),但实际上只是给线程设置⼀个中断标志,线程仍会继续运,⾏,在执行sleep、join等操作的时候会抛出InterruptedException异常。

  • public static boolean interrupted()

作⽤是测试当前线程是否被中断(检查中断标志),返回⼀个boolean并清除中断状态,第
⼆次再调⽤时中断状态已经被清除,将返回⼀个false

  • public boolean isInterrupted()

作⽤是只测试此线程是否被中断 , 不清除中断状态

BlockingQueue

BlockingQueue 常用方法:
在源码分析中,有多处调用了BlockingQueue的方法,来看下它们的区别:

抛出异常 特殊值 阻塞 超时
插⼊ add(e) offer(e) put(e) offer(e, time, unit)
移除 remove() poll() take() poll(time, util)
检查 element() peek() 不可⽤ 不可⽤
  • add(anObject):把anObject加到BlockingQueue⾥,即如果BlockingQueue可以容纳,则返回true,否
    则招聘异常
  • offer(anObject):表示如果可能的话,将anObject加到BlockingQueue⾥,即如果BlockingQueue可以
    容纳,则返回true,否则返回false.
  • put(anObject):把anObject加到BlockingQueue⾥,如果BlockQueue没有空间,则调⽤此⽅法的线程
    被阻断直到BlockingQueue⾥⾯有空间再继续.
  • poll(time):取⾛BlockingQueue⾥排在⾸位的对象,若不能⽴即取出,则可以等time参数规定的时间,取
    不到时返回null
  • take():取⾛BlockingQueue⾥排在⾸位的对象,若BlockingQueue为空,阻断进⼊等待状态直到
    Blocking有新的对象被加⼊为⽌

其中: BlockingQueue 不接受 null 元素。试图 add 、 put 或 offer ⼀个 null 元素时,某些实现会
抛出 NullPointerException 。 null 被⽤作指示 poll 操作失败的警戒值。

常用的几种BlockingQueue:

  • ArrayBlockingQueue(int i):它是⼀个有界的阻塞队列,其内部实现是将对象放到⼀个数组⾥。⼀但初始化,大小就⽆
    法修改。

  • LinkedBlockingQueue()或者(int i):它内部以⼀个链式结构(链接节点)对其元素进⾏存储。可以指定元素上限,否则,上限
    则为Integer.MAX_VALUE。

  • DelayQueue:它对元素进⾏持有直到⼀个特定的延迟到期。注意:进⼊其中的元素必须实现Delayed接⼝。

  • PriorityBlockingQueue()或者(int i):它是⼀个⽆界的并发队列。⽆法向这个队列中插⼊null值。所有插⼊到这个队列中的元素必须实现Comparable接⼝。因此该队列中元素的排序就取决于你⾃⼰的Comparable实现

  • SynchronizedQueue():它是⼀个特殊的队列,它的内部同时只能够容纳单个元素。如果该队列已有⼀个元素的话,那么试图向队列中插⼊⼀个新元素的线程将会阻塞,直到另⼀个新阿城将该元素从队列中抽⾛。同样的,如果队列为空,试图向队列中抽取⼀个元素的线程将会被阻塞,直到另⼀个线程向队列中插⼊了⼀条新的元素。因此,它其实不太像是⼀个队列,⽽更像是⼀个汇合点。

RejectedExecutionHandler 拒绝策略

  • AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
  • DiscardPolicy:丢弃任务,但是不抛出异常。
  • DiscardOldestPolicy:丢弃队列中最前⾯的任务,然后重新尝试执⾏任务。
  • CallerRunsPolicy:由调⽤线程处理该任务。

总结

通过例子出发对ThreadPoolExecutor进行源码解析,对execute、addWorker、runWorker、getTask、runWorkerFail等方法做了详细说明。之后还对源码解析中遇到的中断方法、队列、拒绝策略做了详细说明。
除了corePool、maxnumPoolSize,其它所有的组件我们都可以灵活的调整,可以很方便的基于ThreadPoolExecutor定制属于我们自己的线程池。

Java 线程池ThreadPoolExecutor的应用与源码解析相关推荐

  1. java executor 源码_Java线程池ThreadPoolExecutor深度探索及源码解析

    我们的程序里,时常要使用多线程.因此多线程的管理变的尤为重要.ThreadPoolExecutor很好的解决了这一点.本篇文章主要从源码入手,分析ThreadPoolExecutor的原理. 1.标记 ...

  2. Java 线程池(ThreadPoolExecutor)原理分析与使用 – 码农网

    线程池的详解 Java 线程池(ThreadPoolExecutor)原理分析与使用 – 码农网 http://www.codeceo.com/article/java-threadpool-exec ...

  3. JAVA8线程池THREADPOOLEXECUTOR底层原理及其源码解析

    小侃一下 1. 使用线程池的好处. 为什么要使用线程池? 2. 线程池核心参数介绍 3. 提交任务到线程池中的流程 3.1 ThreadPoolExecutor#execute方法整体流程 3.2 排 ...

  4. 深入理解线程池(ThreadPoolExecutor)——细致入微的讲源码。

    在上一篇博文<图解线程池原理>中,大体上介绍了线程池的工作原理. 这一篇从源码层面,细致剖析,文章会很长. 如果上篇文章内容没吸收,先看上篇,先易后难嘛. 本文源码是 java 1.8 版 ...

  5. java线程池ThreadPoolExecutor类详解

    线程池有哪些状态 1. RUNNING:  接收新的任务,且执行等待队列中的任务 Accept new tasks and process queued tasks  2. SHUTDOWN: 不接收 ...

  6. 【源码阅读计划】浅析 Java 线程池工作原理及核心源码

    [源码阅读计划]浅析 Java 线程池工作原理及核心源码 为什么要用线程池? 线程池的设计 线程池如何维护自身状态? 线程池如何管理任务? execute函数执行过程(分配) getTask 函数(获 ...

  7. Java线程池ThreadPoolExecutor使用和分析

    Java线程池ThreadPoolExecutor使用和分析(一) Java线程池ThreadPoolExecutor使用和分析(二) Java线程池ThreadPoolExecutor使用和分析(三 ...

  8. Java线程池ThreadPoolExecutor使用和分析(三) - 终止线程池原理

    相关文章目录: Java线程池ThreadPoolExecutor使用和分析(一) Java线程池ThreadPoolExecutor使用和分析(二) - execute()原理 Java线程池Thr ...

  9. JAVA线程池(ThreadPoolExecutor)源码分析

    JAVA5提供了多种类型的线程池,如果你对这些线程池的特点以及类型不太熟悉或者非常熟悉,请帮忙看看这篇文章(顺便帮忙解决里面存在的问题,谢谢!):     http://xtu-xiaoxin.ite ...

最新文章

  1. javascript函数式_JavaScript中的函数式编程—结合实际示例(第1部分)
  2. Ubuntu 使用国内apt源
  3. 协议crc计算_从零了解modbus协议 第三篇
  4. python中fork创建新的进程
  5. java.lang.OutOfMemoryError---at java.lang.StringBuilder.append
  6. 如何用js得到当前页面的url信息方法
  7. 【C语言】第八章 地址操作与指针 题解
  8. PATH变量,cp,mv,cat,more,less,head,tail命令举例
  9. Python学习总结(5)——字符串
  10. CAVLC和CABAC简介
  11. 干货|一文看懂BLE低功耗技术-附主流BLE芯片厂商介绍
  12. 深思新推出高性价比智能卡加密锁--魔锐1
  13. 新浪微博情感分析--含爬虫及数据分析
  14. xdm,程序员外包能干吗?
  15. CentOS中使用VeraCrypt(二):加密卷类型
  16. java 二进制 表示负数_java中的负数表示
  17. m73p黑苹果_M93P一次性拔草小主机,黑苹果,软路由。
  18. 【LeetCode-中等】55. 跳跃游戏(详解)
  19. 小滴课堂工业级paas云平台+springcloudAlibaba+JDK11综合项目实战
  20. 一个简单的仓库管理系统

热门文章

  1. Java中选择排序,冒泡排序,插入排序,快速排序
  2. Android小知识-了解下Android系统的显示原理
  3. Postgres XL 集群中各节点的角色和作用
  4. Scala 中的集合(三):实现一个新的 Collection 类
  5. vijos1196|吃糖果游戏|博弈论
  6. 没有找到MSVCP71.dll,迅雷5无法进行离线下载,P2P Seacher无法连入emule网络
  7. Java之WeakReference与SoftReference使用讲解
  8. 用C实现的一个Bash脚本
  9. 当程序员那么痛苦,我来告诉你他们为什么还没放弃?
  10. 干货首发,能够清理,带动画的自己定义控件CuteEditText