ThreadPoolExecutor源码详解
ExecutorService使用线程池中可用的线程执行每个提交的任务,这些线程通常都是使用工厂方法配置
线程池解决两种不同的问题:提高处理大量异步任务的性能(通过减少每个线程的唤醒时间)
提供一种管理和限制线程池的手段
每个ThreadPoolExecutor都含有一些基本的静态数据成员,比如已经完成的任务数
为了可以在一系列背景下可以使用,ThreadPoolExecutor提供了很多可以调节的参数还有扩展的钩子
线程池最大线程个数
线程池通过最大线程数和核心线程数调节线程池中线程的数量,如果一个任务通过execute(Runnable)方法提交,并且运行的
线程少于核心线程的数量,就会创建新的线程处理请求(尽管会有其他空闲的工作线程)。如果运行的线程数量大于核心线程数,
少于最大线程数量,只有在任务队列已经满了的情况下才会创建新的线程。将两者的数量设置成相同的,创建的就是固定数量的线程池。
将最大的线程数量设置成Integer.MAX_VALUE,这就意味着你允许任意数量的并发任务,最大线程数量和核心线程数量是在你
创建线程池的时候定好的,也可以使用setCorePoolSize和setMaximumPoolSize两个方法设置。
默认的 情况下,核心线程只会在任务到达的过程中创建,不过可以使用下面两个函数动态的改变prestartCoreThread和
prestartAllCoreThreads,使用者种情况之一就是你在创建线程池的时候,你的任务队列不是NULL
创建新的线程
新线程是使用ThreadFactory创建的,如果没有特殊的声明,默认情况下使用defaultThreadFactory,这种创建出来的线程
具有相同的线程组、线程优先级(NORM_PRIORITY)和非守护线程状态。通过使用不同的线程工厂,你可以改变上面所说的各种
配置信息。如果ThreadFactory使用newThread创建线程失败,executor将会继续,但是不能执行任何任务。线程应该处理
"modifyThread"(什么@code RuntimePermission)如果工作线程或者其他线程使用这个线程池,但是不处理这个允许权
限,服务将会被降级:配置改变将不会及时生效关闭线程池将会处在一个可以终结但是没有全部结束的状态(还是存在线程)。
存活时间
如果线程池含有的线程大于核心线程的数量,多余的空闲线程就会在超过存活时间之后就会被终结。这就提供了一种当线程池不再
活跃的时候减少线程池资源消耗的一种手段。如果后面线程池在此活跃,就会创建新的线程。通过函数setKeepAliveTime(long, TimeUnit)
设置具体的时间参数。上面的是针对超过核心线程数的额外线程,可以通过函数allowCoreThreadTimeOut(boolean)同样给
核心线程也设置类似上面的功能。
排队
任何形式的BlockingQueue都可以用来存储所提交的任务。队列的使用和线程池的大小相关。如果小于核心线程数肯定是去创建
线程而不是排队。如果运行的线程数量大于或者等于核心线程数量,他就会排队,而不是创建线程。如果一个请求不能排队(队列满了),
这时候会创建线程,除非这会超过最大的线程数量。这种情况下,任务将会被拒绝。
下面有三种基本的排队策略:<1>工作队列的一种好的选择就是SynchronousQueue,在没有线程可以运行任务的时候,将一个任
务进行排队将会失败,所以将会新建一个线程,这种可以避免处理一批任务,但是任务之间存在依赖
关系出现的死锁现象。这种直接的切换就需要无界的最大线程数量。,任务到达的速度超过处理的速度,
线程的数量就会无限制的增长。
<2>无界队列,比如LinkedBlockingQueue,这种情况下如果线程数量和核心线程数量相同,到达的任务
将会在队列中等待,这种适合在任务之间相互独立的时候使用。这样每个任务的执行就不会影响其他任务的执行。
例如在网页服务器中突然出现大量的请求。处理速度就很慢的情况下,这种可以允许任务的无限制到达,
随后处理(类似消息队列中的削峰功能)
<3>有限制的队列,比如ArrayBlockingQueue,防止资源耗尽。但是很难调节和控制。队列和核心线程
的大小可以相互折中,使用小的核心线程数量大的队列减少CPU、系统资源和线程切换的消耗。但是系统的吞吐量减少。
如果一个线程经常阻塞,即使使用大的线程池数量小的队列(这种CPU会负载大),这种情况下可能会遇到无法接受任务。
还是会减少系统的吞吐量。
拒绝任务
executor处在shutdown状态的话,就会拒绝任务。还有一种情况就是,如果使用固定的最大线程数和队列容量,当两者饱和的时候就会出现上面的情况。
拒绝策略:<1> ThreadPoolExecutor.AbortPolicy是默认策略,在拒绝的时候抛出RejectedExecutionException异常。
<2> ThreadPoolExecutor.CallerRunsPolicy,执行execute(Runnabel)方法的线程执行这个任务,这种会减缓新任务提交的速率。
<3> ThreadPoolExecutor.DiscardPolicy,不能执行的直接抛弃。
<4> ThreadPoolExecutor.DiscardOldestPolicy,队列最前面的任务被丢弃
钩子方法
提供可以重写的方法beforeExecute(Thread, Runnable)和afterExecute(Runnable, Throwable),这些可以在执行任务之前或者之后处理一些信息。
比如初始化Threadlocals,如果你想在在executors终止之后处理额外的东西,你可以重写terminated方法
终止
当线程池在程序中不在引用,并且不含有现成的时候将会被自动shutdown。如果你要确保在线程池会在用户忘记的情况下也会被回收,
这时候你就要设置allowCoreThreadTimeOut(boolean)
线程池主要的控制状态ctl是个原子类型的整形。workCount表示有效的线程数(整形的低29位表示),(允许开始线程,但是不允许结束的线程数量),
可能和存货的线程数量稍有不同,比如创建线程失败(上面有介绍)。runState表示线程池的状态。(ctl高3位表示)
一个任务提交给线程池的运行图
介绍完上面后,我们从execute()方法开始分析
这里面就是上面
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); int c = ctl.get(); 1、少于核心线程数的线程在运行,尝试创建新的线程执行任务,当前的任务就是firstTask,就是调用addWorker(),方法 if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); } 2、如果任务排队了,现在要进行双重验证:《1》判断是不是在进入方法的时候进入了shutdown状态,这样就回滚,采取对应的拒 绝策略 《2》判断是不是对应线程数是0,是的话就会启动一个空线程(用来执行后面的任务)。 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); } 3、到这一步说明队列已经满了,那就尝试启动一个线程执行任务,如果不能启动(现在活的线程数量到达最大线程数),这时候采应上面所说的相应拒绝策略。
这里函数中的false表示和最大线程数比较。 else if (!addWorker(command, false)) reject(command); }
addworker()方法
获取ctl值,里面存放的是运行状态和运行的线程数 進入for循环 以下三种情况不能向队列中添加任务:状态在shutdown之后(stop tidying terminated),关闭了当然不需要 状态除了running情况下,任务是空(空任务不需要添加) 状态除了running情况下,队列不是空的 潜台词就是running状态,或者是在shutdown状态但是firstTask==null且队列不是空(后者用于启动一个空线程执行任务队列的)才会添加线程 否则就进下一个for循环 线程的数量是不是达到一定的值,达到了就不能添加,否则可以添加,将线程的数量加一,成功就跳出循环,否则就判断线程池的状态,不一致就从外循环重新开始。 线程数加一了,下面进行线程的增加 使用一个任务构造一个Worker类,在Worker类的构造函数中使用线程工厂和当前的Worker类(本身实现了Runnable接口)生成一个新的线程。添加的过程中上了主锁 如果是running状态或者在shutdown状态但是任务是空的状态下可以添加线程,将对应的线程添加到线程池workers;,启动线程,如果启动失败,使用函数 addWorkerFailed(w)回滚 private boolean addWorker(Runnable firstTask, boolean core) { retry: for (;;) { int c = ctl.get(); int rs = runStateOf(c); // 不是 running状态或者 shutdown状态但是任务队列不是空且firsttask是空,不是这两种情况不会添加线程。 if (rs >= SHUTDOWN &&! (rs == SHUTDOWN &&firstTask == null &&! workQueue.isEmpty())) return false; for (;;) { int wc = workerCountOf(c);
线程总数超过相应的数量时候不准添加线程。 if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false; if (compareAndIncrementWorkerCount(c)) break retry; 增加线程成功,跳出外循环。 c = ctl.get(); if (runStateOf(c) != rs) continue retry; // CAS操作失败,重试 } } 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. int rs = runStateOf(ctl.get()); 在此判断线程池的状态,和上一个判断是一样的。 if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { if (t.isAlive()) // precheck that t is startable throw new IllegalThreadStateException(); workers.add(w); int s = workers.size(); if (s > largestPoolSize) largestPoolSize = s; 记录最大线程数 workerAdded = true; } } finally { mainLock.unlock(); } if (workerAdded) { t.start(); 这里启动线程 workerStarted = true; } } } finally { if (! workerStarted) addWorkerFailed(w); } return workerStarted; }
启动线程之后,执行的是worker中的run()方法,这个方法的里面就runWorker(this)这一个函数。下面我们看看这个函数
runWorker()
这个函数是由启动的线程执行的,每次线程都会从任务队列中取任务然后执行(执行任务之前会检查线程池的状态,满足相应的条件,直接设置中断状态,并不做其他动作,继续执行任务,注意由于是blockingqueue,所以设置中断后,线程尝试获取任务时会抛出异常终止),直到任务队列为空,最后会执行tryTerminate()函数查看是不是满足可以终止的条件
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(); // pool处于stop状态就直接将线程中断 // if not, ensure thread is not interrupted. This // requires a recheck in second case to deal with // shutdownNow race while clearing interrupt
running或者shutdown状态就会清除标志位,否则就会设置标志位 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); } }
下面分析上面标记颜色的两种方法
getTask()
这里有个东西设计的很巧妙,就是线程池处在shutdown状态队列是空或者shutdown之后的状态需要终止线程池,这个时候通过getTask()函数返回的就是null,这个NUll在runWorker()中直接回跳出循环执行processWorkerExit(w, completedAbruptly)函数终止线程,任务队列空之后也会执行这个方法。
private Runnable getTask() { boolean timedOut = false; // Did the last poll() time out? for (;;) { int c = ctl.get(); int rs = runStateOf(c); // shutdown状态队列是空或者shutdown之后的状态,线程不需要继续执行任务了返回null,(因为这些状态是要关闭线程池的)
最后在runworker()方法中跳出循环执行tryterminate()判断状态以及任务数判断是否尝试结束线程池 if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { decrementWorkerCount(); return null; } int wc = workerCountOf(c); // Are workers subject to culling? boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; 大于核心线程数,或者线程超时就会返回null,在runWorker中因为返回null退出循环,直接结束线程 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(); if (r != null) return r; 返回任务队列中的任务 timedOut = true; 如果能执行到这一步就说明执行超时,重新去尝试获取任务 } catch (InterruptedException retry) { timedOut = false; } } }
processWorkerExit(w, completedAbruptly);
private void processWorkerExit(Worker w, boolean completedAbruptly) { if (completedAbruptly) // 如果突然退出 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);//小于核心线程数量,线程不会被回收,但是上面已经remove一个了, } 所以这里又添加一个(保证小于核心线程的时候不会回收),其实没有做到真正的线程挂起或者循环,
只是在循环中不断的终止线程启动线程。
}
最后tryterminate(),用来终止一个线程
final void tryTerminate() { for (;;) { int c = ctl.get(); if (isRunning(c) || 运行状态 runStateAtLeast(c, TIDYING) || tidying状态或者terminate状态,这时候没有线程可以结束了,直接执行钩子方法结束线程池 (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty())) shutdown状态但是任务队列不是空,还要线程执行任务,不结束线程 return; if (workerCountOf(c) != 0) { // 结束一个线程 interruptIdleWorkers(ONLY_ONE); return; } final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) { 到这里就说明线程是0,任务是0,所以直接设置tidying状态 try { terminated(); 执行钩子方法 } finally { ctl.set(ctlOf(TERMINATED, 0)); 结束线程池 termination.signalAll(); } return; } } finally { mainLock.unlock(); } // else retry on failed CAS } }
线程池的关闭
public void shutdown() { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { checkShutdownAccess(); advanceRunState(SHUTDOWN); 设置状态 interruptIdleWorkers(); 中断线程池中的所有线程 onShutdown(); // 钩子方法可以自己继承扩展 } finally { mainLock.unlock(); } tryTerminate(); 最后检查线程池的状态尝试终止 }
这种线程的终止方式,是使用中断抛出异常的,终止所有在getTask()方法上等待的线程,但是这种不能停止所有线程(正在执行任务还有正在获取任务的线程,这时候就需要下面一种结束方式),至于为什么可以设置中断就可以中断线程因为blockingqueue中的poll()方法使用的是lock.lockInterruptibly()函数上的锁,这种就会设置中断后直接导致等待的线程抛出interruptedexception退出。
还有一种就是直接在getTask()函数中设置返回null,线程跳出循环,将自己的引用设置空,最终等待垃圾回收
shutDown()
当线程池调用该方法时,线程池的状态则立刻变成SHUTDOWN状态。此时,则不能再往线程池中添加任何任务,否则将会抛出RejectedExecutionException异常。但是,此时线程池不会立刻退出,直到任务队列的任务都已经处理完成,才会退出。
shutdownNow()
根据JDK文档描述,大致意思是:执行该方法,线程池的状态立刻变成STOP状态,并试图停止所有正在执行的线程,不再处理还在池队列中等待的任务,当然,它会返回那些未执行的任务。
它试图终止线程的方法是通过调用Thread.interrupt()方法来实现的,但是大家知道,这种方法的作用有限,如果线程中没有sleep 、wait、Condition、定时锁等应用, interrupt()方法是无法中断当前的线程的。所以,ShutdownNow()并不代表线程池就一定立即就能退出,它可能必须要等待所有正在执行的任务都执行完成了才能退出。