在并发编程的过程中,创建线程是十分消耗资源的,甚至可能会引发内存溢出,线程池技术应运而生。在Java中,几乎所有需要异步或并发执行任务的程序都需要利用线程池进行调度,合理设计的线程池能够带来以下优势:
降低资源消耗。通过对线程进行重复利用,降低线程创建和销毁的性能损耗。
提高响应速度。当任务到达时,省去线程创建的步骤,线程池直接调度线程执行任务。
实现线程的统一管理。线程是稀缺资源,利用线程池可以对其进行统一分配、调优和监控。
Java中内置了一套完备的线程池框架,想要在开发过程中根据业务场景合理、高效地使用线程,必须全面了解其内部的工作原理。
一个基于线程池模型的多线程程序在并发处理多个任务时,通常对应着两级调度结构:
在上层应用程序中,用户级的调度器将这些任务映射为固定数量的线程
在底层,操作系统内核将这些线程映射到CPU上
在HotSpot VM的线程模型中,Java线程(Thread对象)被一对一映射为本地操作系统的线程。一个Java
线程的启动和终止,对应着一个本地操作系统线程的创建和回收。
Java的线程池调度模型中,以Executor接口为核心的Executor框架充当了用户级调度器的角色,其包含的主要的类与接口如下:
Executor框架主要由三部分组成:
Runnable、Callable、Future是三个与线程池框架关联十分紧密的接口:
Runnable、Callable都是函数式接口,两者都是为类实例可能由另一个线程执行的场景(例如将Runnable或Callable任务提交到线程池中)设计的,区别在于Callable接口可以返回计算结果
Future接口用于表示异步任务的执行结果。 其提供了检查任务是否完成、等待任务完成以及获取任务结果的能力。
本节侧重介绍Future接口在JDK线程池框架中扮演的角色,关于其具体的实现原理可以参考笔者的另一篇原创文章:Future接口的实现原理 。
JUC.ExecutorService中定义了向线程池提交Runnable、Callable任务的抽象方法,其具体实现在抽象类JUC.AbstractExecutorService中:
/**
* 提交Runnable任务,并返回一个代表该任务的Future。
* Future的get()方法将在任务执行成功后返回null。
*/
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
//将Runnable任务封装成RunnableFuture
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask); //将任务交给线程池执行
return ftask;
}
/**
* 提交有返回值的任务,并返回Future
* Future的get()方法在任务执行成功后返回任务的计算结果。
*/
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
//将Callable任务封装成RunnableFuture
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable; //Callable任务直接赋值
this.state = NEW;
}
public FutureTask(Runnable runnable, V result) {
//Runnable任务需要转换成Callable再赋值
this.callable = Executors.callable(runnable, result);
this.state = NEW;
}
Runnable到Callable的转换其实很简单,适配器模式的经典应用:
FutureTask作为内置在JDK中的Future接口实现类,在线程池的框架中起到了举足轻重的作用:
将Runnable和Callable任务进行了统一的封装,使线程池无需关心上层任务的具体类型,只需负责调度线程执行任务即可
作为任务提交之后的返回值,用于获取任务的执行结果
在Java的线程池框架中,Executor接口是任务执行机制的核心,Executor以及其子接口ExecutorService提供了任务执行涉及到的通用能力,包括任务的提交、调度运行和终止等等。
Executor接口只定义一个excute方法,用于执行提交的Runnable任务
Executor接口的作用是将任务提交与每个任务将如何运行的机制(括线程使用、调度等的细节)解耦
内存一致性保证:线程中将Runnable任务提交给Executor之前的操作happens-before任务开始执行(可能由另一个线程执行)
/**
* 在之后的某个时间执行提交的任务。
* 该任务可以在新线程、线程池或调用线程中执行,取决于Executor的具体实现。
*
* @throws RejectedExecutionException if this task cannot be
* accepted for execution
*/
void execute(Runnable command);
ExecutorService继承自Executor接口,在Executor接口已有的任务执行能力的基础上:
内存一致性保证:线程中将任务提交给ExecutorService之前的操作happens-before对该任务采取的任何操作,而这些操作又happens-before通过Future.get()获取任务执行结果
/**
* 提交有返回值的任务,并返回Future,Future的get()方法在任务执行成功后返回任务的执行结果。
*
* @throws RejectedExecutionException if the task cannot be
* scheduled for execution
*/
<T> Future<T> submit(Callable<T> task);
/**
* 提交Runnable任务,并返回一个代表该任务的Future。
* Future的get()方法将在任务执行成功后返回null。
*
* @throws RejectedExecutionException if the task cannot be
* scheduled for execution
*/
Future<?> submit(Runnable task);
/**
* 启动有序的关闭程序,关闭过程中仍会执行已提交的任务(正在执行和队列中的),但不会接收新任务
* 该方法不会等待已提交的任务执行完成,如果需要,使用#awaitTermination方法
*/
void shutdown();
/**
* 启动有序的关闭程序,不再接收新任务
* 尝试停止所有正在执行的任务(不是一定能够停止,典型的实现通过Thread#interrupt取消,这意味着未能响应中断的任务不会终止执行),停止等待任务的处理,并返回等待执行的任务列表。
* 此方法不会等待正在执行的任务终止。 如果需要,使用#awaitTermination方法
*/
List<Runnable> shutdownNow();
/**
* 在关闭请求后阻塞,直到所有任务都完成执行,或者发生超时,或者当前线程被中断,以先发生者为准。
*
* @return {@code true}: executor终止
* {@code false}:在终止前超时
* @throws InterruptedException if interrupted while waiting
*/
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
Executor框架中,线程池有两个原生的实现类,分别为ThreadPoolExecutor和ScheduledThreadPoolExecutor
ThreadPoolExecutor中通过位运算表征线程池的运行状态
private static final int COUNT_BITS = Integer.SIZE - 3; //29
//00011111111111111111111111111111
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
//11100000000000000000000000000000
private static final int RUNNING = -1 << COUNT_BITS;
//00000000000000000000000000000000
private static final int SHUTDOWN = 0 << COUNT_BITS;
//00100000000000000000000000000000
private static final int STOP = 1 << COUNT_BITS;
//01000000000000000000000000000000
private static final int TIDYING = 2 << COUNT_BITS;
//01100000000000000000000000000000
private static final int TERMINATED = 3 << COUNT_BITS;
private static int runStateOf(int c) { return c & ~CAPACITY; }
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
//初始值线程池状态为RUNNING、线程数为0
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private final BlockingQueue<Runnable> workQueue;
private final HashSet<Worker> workers = new HashSet<Worker>();
corePoolSize:核心线程数,由volatile修饰
maximumPoolSize:最大线程数,由volatile修饰,最大值受限于CAPACITY
RUNNING:线程池可以接收新的任务和执行已入队的任务
SHUTDOWN:线程池处不接收新任务,但不影响正在执行的任务,且能处理已入队的任务
STOP:线程池处不接收新任务,移除已经入队的任务且不处理,同时会中断正在执行的任务
TIDYING:线程池中所有的任务已终止,线程数为0;线程池变为TIDYING状态时,会执行钩子函数terminated(),默认实现为空
TERMINATED:钩子函数terminated()被执行完成
线程池一旦被创建(比如调用Executors.newFixedThreadPool()方法),就处于RUNNING状态,并且线程池中的线程数为0
RUNNING —> SHUTDOWN:调用线程池的shutdown()方法
( RUNNING or SHUTDOWN ) —> STOP:调用线程池的shutdownNow()方法
SHUTDOWN —> TIDYING:任务队列为空并且线程池中不存在工作线程
STOP —> TIDYING:线程池中不存在工作线程
TIDYING —> TERMINATED:钩子函数terminated()被执行完成
当ThreadPoolExecutor线程池接收到一个待处理的任务之后,其具体的调度逻辑是在execute()方法中实现的:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//分支1:当前工作线程数 < 核心线程数
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true)) //添加一个核心线程执行当前任务
return;
c = ctl.get(); //若因并发问题导致添加失败,需要重新获取ctl(保证后续逻辑clt的准确性)
}
//分支2:在线程池状态为RUNNING的前提下,将任务入队
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command)) //double check线程池状态,非RUNNING则移除command并走拒绝策略
reject(command);
else if (workerCountOf(recheck) == 0) //任务入队成功后,若不存在工作线程需要手动创建一个非核心线程
addWorker(null, false);
}
//分支3:任务队列已满导致入队失败,需要创建一个非核心线程执行当前任务,若创建失败则走拒绝策略
else if (!addWorker(command, false))
reject(command);
结合execute()方法的源码,总结线程池中任务的主要调度流程如下:
下面我们将更深入地剖析源码,揭秘线程池中线程的创建、任务的真正执行、线程复用以及拒绝策略等细节。
该方法的主要作用就是在线程池中添加工作线程并执行任务,两个参数:
该方法的调用场景分3种情况:
该方法只有在线程添加完成且成功启动的情况下才会返回true
我们将该方法分成两个部分剖析,因为实在是太长了(再次感叹Doug Lea的脑回路)!!!!
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
//1.状态检查
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize)) //2.容量检查
return false;
if (compareAndIncrementWorkerCount(c)) //3.cas更新工作线程数量
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
}
}
...
线程池状态检查时,满足下列两种场景之一,才继续后续逻辑,否则直接返回false
容量检查失败则直接返回false
容量检查通过后,会尝试通过CAS更新工作线程数(ctl属性的低29位)
private boolean addWorker(Runnable firstTask, boolean core) {
...
...
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; //1.加全局锁
mainLock.lock();
try {
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive())
throw new IllegalThreadStateException();
workers.add(w); //2.添加工作线程
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock(); //3.释放全局锁
}
if (workerAdded) {
t.start(); //4.启动线程
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
Worker(Runnable firstTask) {
setState(-1);
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this); //这里我们终于看到了线程产生之处(工厂模式创建一个新的线程)
}
Worker类中run()方法的逻辑如下(剔除掉了一些非核心逻辑):
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 {
while (task != null || (task = getTask()) != null) {
w.lock(); //1.设置独占锁
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted()) //2.中断的处理
wt.interrupt();
try {
Throwable thrown = null;
try {
task.run(); //3.执行任务
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
}
} finally {
task = null;
w.completedTasks++;
w.unlock(); //4.释放锁
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly); //5.工作线程退出
}
}
执行上述逻辑的线程,就是Worker对象对应的工作线程
工作线程真正执行任务逻辑前,会先设置独占锁,这样根据独占锁的状态可以判断Worker对应的线程是否在处理任务(这个状态的消费后文会解析)
线程中断逻辑:
总结一下这段代码的整体逻辑:
工作线程进入runWorker()的任务执行逻辑后,并不是执行执行完当前任务就结束其使命了,即线程池中的线程是能够复用的,而其中关窍正是在runWorker()的循环判断条件中:
private Runnable getTask() {
boolean timedOut = false;
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {//1.线程池状态检查
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) { //2.处理超时
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try { //3.取任务
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
- 当线程池不需要再处理队列中的任务时,getTask()将返回null,调用getTask()方法的工作线程执行结束,工作线程数-1。这个分支对应两种场景:
- 局部变量timed表征从阻塞任务取队列时是否有超时时间:
- 如果取任务超时,且满足以下条件之一,getTask()将返回null,调用getTask()方法的工作线程执行结束,工作线程数-1:
梳理getTask()的整体逻辑可以看出,如果核心线程数已满或者allowCoreThreadTimeOut为true,线程在获取任务超时之后,就会退出任务的执行逻辑,因为线程池认为此时池中的工作线程数量供大于求,这体现了线程池动态调整其工作线程数的策略和思想。
参考executer()方法,可以看出线程池在以下两种场景会执行拒绝策略:
任务入队后,double check线程池状态,如果非RUNNING状态则移除command并执行拒绝策略
任务队列已满导致入队失败,需要创建一个非核心线程执行当前任务,若已经达到最大线程数则执行拒绝策略
private volatile RejectedExecutionHandler handler;
final void reject(Runnable command) {
handler.rejectedExecution(command, this);
}
handler即为拒绝策略的处理器,是线程池的一个实例变量,通常在线程池创建时传入,JDK中有四种实现:
AbortPolicy:默认的拒绝策略,任务无法处理时直接抛出RejectedExecutionException异常,适合主调线程不可阻塞且FailFast场景
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
DiscardPolicy/DiscardOldestPolicy:适合主调线程不可阻塞且FailSafe场景
//do nothing 直接丢弃任务
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {}
//在线程池未关闭的情况下,抛弃任务队列中最旧的任务也就是最先加入队列的,再将新任务尝试添加进去,若线程池关闭则任务r被丢弃
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
CallerRunsPolicy:caller线程直接运行任务,可能会block caller线程,适合不能接受任务丢失、可阻塞主调线程的场景使用
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
ThreadPoolExecutor执行execute方法分下面 4 种情况。
ThreadPoolExecutor的总体设计思路,是为了在执行 execute()方法时,
尽可能地避免获取全局锁(那将会是一个严重的可伸缩瓶颈)。在ThreadPoolExecutor完成预热之后(当前运行的线程数大于等于
corePoolSize),几乎所有的execute()方法调用都是执行步骤2(入队),不需要获取全局锁。
调用线程池的shutdown()方法后,线程池将变成SHUTDOWN状态,不再接收新任务,但会处理完正在运行的和在阻塞队列中等待的任务。shutdown()方法的主要逻辑如下:(剔除了部分非核心代码)
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock(); //全局锁,可重入
try {
//1.CAS将线程池状态设置为SHUTDOWN
advanceRunState(SHUTDOWN);
//2.中断所有空闲线程
interruptIdleWorkers();
} finally {
mainLock.unlock();
}
//3.尝试终止线程池
tryTerminate();
}
interruptIdleWorkers(false):中断所有空闲的Worker线程
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock(); //全局锁,可重入
try {
for (Worker w : workers) {
Thread t = w.thread;
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
结合上文中runWorker方法中的逻辑,线程池中的工作线程(Worker)可以划分为两类:
参考Worker类中的tryAcquire()方法可以看出,Worker锁不可重入,因此只有空闲Worker其tryLock才会返回true,也即该方法仅会将空闲Worker的中断标识置为true
中断信号发出后,对应的阻塞在getTask()获取任务的空闲Worker退出阻塞状态,getTask()将return null,并执行对应Worker线程的退出逻辑
private Runnable getTask() {
boolean timedOut = false;
for (;;) {
...
...
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {//2.线程池状态检查返回null
decrementWorkerCount();
return null;
}
...
...
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) { //1.中断信号发出,退出阻塞态
timedOut = false;
}
}
}
运行中的Worker不会收到中断信号,运行结束后,会从队列中阻塞获取任务,如果队列为空,又因为线程池处于SHUTDOWN状态,不会接收新任务,所以会一直阻塞在任务获取逻辑中,导致线程无法终止!!!这就需要在shutdown()后,仍可以发出中断信号。
final void tryTerminate() {
for (;;) {
int c = ctl.get();
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
//走到这里有两种情况:
//1.STOP状态
//2.SHUTDOWN状态且workQueue为空
if (workerCountOf(c) != 0) {
//中断一个正在等任务的空闲worker
//收到信号后再次判断线程池状态,会return null,执行线程的退出逻辑
interruptIdleWorkers(ONLY_ONE);
return;
}
//走到这里,开始执行terminate逻辑:
//1.SHUTDOWN状态,且workQueue为空,且worker数为0
//2.STOP状态,且worker数为0
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//CAS将状态变成TIDYING
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
terminated();//调用钩子函数,需要子类实现
} finally {
ctl.set(ctlOf(TERMINATED, 0));//CAS将状态变成TERMINATED
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
// else retry on failed CAS
//CAS失败重新进入循环
}
}
Doug Lea大神巧妙地在所有可能导致线程池产终止的地方安插了tryTerminated()尝试线程池终止的逻辑
该方法的整体逻辑分两个部分:
总结:
调用线程池的shutdownNow()方法后,线程池将变成STOP状态。STOP状态下,线程池处不接收新任务,中断正在执行的任务,移除已经入队的任务不再处理,并返回已经入队的任务列表。
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock(); //全局锁,可重入
try {
//CAS设置线程池状态为STOP
advanceRunState(STOP);
//中断所有worker,包括正在执行任务的
interruptWorkers();
//将workQueue中的元素移除并放入一个List并返回
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate(); //尝试终止线程池
return tasks;
}
interruptWorkers() :对所有Worker调用interruptIfStarted(),其中会判断Worker的AQS state是否大于等于0,再设置线程的中断标识
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers)
w.interruptIfStarted();
} finally {
mainLock.unlock();
}
}
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
至于为什么要加上state的判断,我们看下Doug Lea的注释一目了然:
需要注意的是,对于运行中的线程调用Thread.interrupt()并不能保证线程被终止,task.run()内部可能捕获了InterruptException,没有上抛,导致线程正在执行的任务无法立即终止
总结:
清理一个即将销毁的Worker,只有线程池中Worker对应的线程才会调用该方法:(剔除了部分非核心逻辑)
private void processWorkerExit(Worker w, boolean completedAbruptly) {
if (completedAbruptly)
//只有异常退出的需要在这里做--操作,其他情况都是在getTask()方法里做的
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock(); //全局锁
try {
workers.remove(w); //销毁Worker
} finally {
mainLock.unlock();
}
tryTerminate(); //尝试终止线程池,这个逻辑解释了为什么tryTerminate()中只是中断一个空闲线程
int c = ctl.get();
if (runStateLessThan(c, STOP)) { //仅 < STOP状态(任务队列中可能存在任务)时进入
if (!completedAbruptly) { //非异常退出的场景,如果核心线程允许超时且队列中还有任务,则最小线程数为1,如果核心线程不允许超时,则最小线程数为corePoolSize
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty())
min = 1;
if (workerCountOf(c) >= min)
return; // replacement not needed
}
addWorker(null, false); //任务异常退出或者线程池中线程数不够,需要补齐新增一个线程
}
}
public boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (;;) {
if (runStateAtLeast(ctl.get(), TERMINATED))
return true;
if (nanos <= 0)
return false;
nanos = termination.awaitNanos(nanos);
}
} finally {
mainLock.unlock();
}
}
private final Condition termination = mainLock.newCondition();
termination.awaitNanos() 是通过 LockSupport.parkNanos(this, nanosTimeout)实现的阻塞等待,阻塞等待过程中发生以下具体情况会解除阻塞:
termination.signalAll(),只有在 tryTerminated()尝试终止线程池成功,将线程池更新为TERMINATED状态后才会signalAll(),awaitTermination()再次判断状态会return true退出
达到了超时时间,此时nano==0,再次循环判断return false
当前线程被中断,termination.awaitNanos()会上抛InterruptException,awaitTermination()继续上抛给调用线程
通过 Executor 框架的工具类 Executors,可以创建 3 种类型的 ThreadPoolExecutor。
FixedThreadPool中 corePoolSize
和 maximumPoolSize
都被设置为创建时传入的参数 nThreads
如果线程池中的工作线程数量小于corePoolSize
,则创建新的核心线程来执行任务。
在线程池完成预热之后(工作线程数等于corePoolSize),新的任务将加入 LinkedBlockingQueue
。
工作线程执行完 firstTask后,会在循环中反复从LinkedBlockingQueue
获取任务来执行。
FixedThreadPool使用无界阻塞队列LinkedBlockingQueue
作为线程池的工作队列(队列的容量为 Integer.MAX_VALUE)。
由于不存在阻塞队列已满的场景,maximumPoolSize
和keepAliveTime
都是无效参数,且处于RUNNIG状态的线程池不会拒绝任务
SingleThreadExecutor是使用单个工作线程的线程池。
SingleThreadExecutor的corePoolSize
和maximumPoolSize
被设置为 1。其余参数与FixedThreadPool
一致,两个线程池整体的调度逻辑也没有太大区别
SingleThreadExecutor当第一个工作线程创建后即完成预热,之后新的任务将加入 LinkedBlockingQueue
。
CachedThreadPool中corePoolSize
为0,maximumPoolSize
为 Integer.MAX_VALUE,即maximumPool是无界的。keepAliveTime为60L,意味着 CachedThreadPool 中的空闲线程等待新任务的最长时间为 60 秒,超过60秒后该线程将从线程池中移出。
CachedThreadPool使用SynchronousQueue
作为线程池的工作队列,SynchronousQueue
是一个不存储元素的阻塞队列,每一个put操作必须等待一个take操作,否则不能继续添加元素。
如果主线程提交任务的速度非常高,CachedThreadPool会不断创建新线程(maximumPool是无界的),极端情况下,CachedThreadPool会因为创建过多线程而耗尽CPU和内存资源。
下面章节详解。
注意:线程池的参数配置和具体选型是和业务场景紧密相关的,好多公司都是基于业务场景,在JDK原生的ThreadPoolExecutor封装定制化的线程池。
线程池 | 队列类型 |
---|---|
CachedThreadPool | SynchronousQueue |
SingleThreadExecutor | LinkedBlockingQueue |
FixedThreadPool | LinkedBlockingQueue |
ScheduledThreadPool | DelayedWorkQueue |
这个队列是链表形式的阻塞队列,长度无限制,这个队列用于SingleThreadExecutor
和FixedThreadPool
中,single是fix的特殊情况,core和max设置为1即可。那么可以知道FixedThreadPool线程池可以一直向其中提交任务,线程数量大于core数量的话,任务会被加入到LinkedBlockingQueue中,由于没有长度限制,也就不会出现RejectedExecutionHandler
拒绝策略的触发时机。
CachedThreadPool
使用的该队列,该队列是个阻塞队列,主要使用的两个方法是offer
和take
,一个是插入数据,一个是取出数据。队列有如下性质:
我们可以无限向CachedThreadPool
线程池中添加任务,因为最大线程数是integer,MAX;SynchronousQueue是插入和取出是需要配对出现的,有两种情况会发生:
addworker(command,false)
,创建非核心线程。我们可以看到,在某些情况下CachedThreadPool
会不断窗口新的非核心线程,又没有最大线程数量的限制,所以这个线程池不推荐使用,大量的资源浪费。
SynchronousQueue源码分析
ScheduledThreadPool
使用的是DelayedWorkQueue
,我们知道ScheduledThreadPool
线程池具有timer的类似的功能,可以做定时任务,定时任务的排序,存取操作是由DelayedWorkQueue
实现的,
ScheduledThreadPool是继承自ThreadPoolExecutor,所以大部分函数接口都是相同的
多了一些有定时任务的接口如下:
随便看一个接口:
/**
* @throws RejectedExecutionException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public ScheduledFuture<?> schedule(Runnable command,
long delay,
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
RunnableScheduledFuture<?> t = decorateTask(command,
new ScheduledFutureTask<Void>(command, null,
triggerTime(delay, unit)));
delayedExecute(t);
return t;
}
首先将runable包装成RunnableScheduledFuture,它继承关系:
public interface Delayed extends Comparable<Delayed> {
/**
* Returns the remaining delay associated with this object, in the
* given time unit.
*
* @param unit the time unit
* @return the remaining delay; zero or negative values indicate
* that the delay has already elapsed
*/
long getDelay(TimeUnit unit);
}
定时任务boolean isPeriodic();
方法返回是否是需要重复运行的任务。
compareTo(T o);
用于队列数据进行排序操作的
DelayedWorkQueue的数据结构是一个连续空间的一维数组,空间不够会自动增长50%,内部的数据采用小堆形式存储;
private static final int INITIAL_CAPACITY = 16;
private RunnableScheduledFuture<?>[] queue = new RunnableScheduledFuture<?>[INITIAL_CAPACITY];
/**
* Resizes the heap array. Call only when holding lock.
*/
private void grow() {
int oldCapacity = queue.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // grow 50%
if (newCapacity < 0) // overflow
newCapacity = Integer.MAX_VALUE;
queue = Arrays.copyOf(queue, newCapacity);
}
compareTo
函数比较大小进行插入操作。函数调用链:
offer(Runnable x) -> siftUp(int k, RunnableScheduledFuture<?> key) -> setIndex(RunnableScheduledFuture<?> f, int idx)
public boolean offer(Runnable x) {
if (x == null)
throw new NullPointerException();
RunnableScheduledFuture<?> e = (RunnableScheduledFuture<?>)x;
final ReentrantLock lock = this.lock;
lock.lock();
try {
int i = size;
if (i >= queue.length)
grow();
size = i + 1;
if (i == 0) {
queue[0] = e;
setIndex(e, 0);
} else {
siftUp(i, e);
}
if (queue[0] == e) {
leader = null;
available.signal();
}
} finally {
lock.unlock();
}
return true;
}
首先判断添加之后数据是否需要扩容,需要扩容增加容量50%,然后将数据添加到堆的最后位置,然后从底部向上排序。
函数调用链:
take() -> finishPoll(RunnableScheduledFuture<?> f) -> siftDown(int k, RunnableScheduledFuture<?> key)
-> setIndex(RunnableScheduledFuture<?> f, int idx)
public RunnableScheduledFuture<?> take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {
RunnableScheduledFuture<?> first = queue[0];
if (first == null)
available.await();
else {
long delay = first.getDelay(NANOSECONDS);
if (delay <= 0)
return finishPoll(first);
first = null; // don't retain ref while waiting
if (leader != null)
available.await();
else {
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
available.awaitNanos(delay);
} finally {
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
if (leader == null && queue[0] != null)
available.signal();
lock.unlock();
}
}
首先判断首个元素是不是空,是空的话说明没用元素,需要线程挂起,等待offer操作唤起线程。如果取出首个元素存在,那么需要判断当前时间节点是否到了任务要执行的时间了,如果到了那么poll操作,否则需要当前线程等待一定的时间间隔重复这个过程。