在并发编程的过程中,创建线程是十分消耗资源的,甚至可能会引发内存溢出,线程池技术应运而生。在Java中,几乎所有需要异步或并发执行任务的程序都需要利用线程池进行调度,合理设计的线程池能够带来以下优势:
Java中内置了一套完备的线程池框架,想要在开发过程中根据业务场景合理、高效地使用线程,必须全面了解其内部的工作原理。
一个基于线程池模型的多线程程序在并发处理多个任务时,通常对应着两级调度结构:
在HotSpot VM的线程模型中,Java线程(Thread对象)被一对一映射为本地操作系统的线程。一个Java 线程的启动和终止,对应着一个本地操作系统线程的创建和回收。
Java的线程池调度模型中,以Executor接口为核心的Executor框架充当了用户级调度器的角色,其包含的主要的类与接口如下:
Executor框架主要由三部分组成:
Runnable、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);
}
在线程池的框架中,Runnable和Callable任务都会被封装成RunnableFuture
RunnableFuture继承自Runnable接口和Future接口,因此能够作为任务提交到线程池中执行,也能通过其获取到任务的执行结果
RunnableFuture的具体实现为FutureTask,FutureTask中聚合了Callable类型的实例变量用于存放具体的任务,并且支持Runnable任务到Callable任务的转换,可以说FutureTask实现了Callable和Runnable任务的统一管理
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;
}
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的内部实现,以剖析线程池的工作原理。
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; }
ctl属性:AtomicInteger类型,高3位存储线程池状态,低29位存储线程数量
//初始值线程池状态为RUNNING、线程数为0
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
workQueue属性:阻塞队列,用于存放待执行的任务
private final BlockingQueue<Runnable> workQueue;
workers属性:用于存放工作线程,非线程安全,操作该属性需要获取全局锁
private final HashSet<Worker> workers = new HashSet<Worker>();
线程池的线程容量:
线程池的状态说明:
线程池的状态转换:
当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;
}
}
...
...
}
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.工作线程退出
}
}
工作线程进入runWorker()的任务执行逻辑后,并不是执行执行完当前任务就结束其使命了,即线程池中的线程是能够复用的,而其中关窍正是在runWorker()的循环判断条件中:
下面分析getTask()方法的内部细节:
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()的整体逻辑可以看出,如果核心线程数已满或者allowCoreThreadTimeOut为true,线程在获取任务超时之后,就会退出任务的执行逻辑,因为线程池认为此时池中的工作线程数量供大于求,这体现了线程池动态调整其工作线程数的策略和思想。
参考executer()方法,可以看出线程池在以下两种场景会执行拒绝策略:
private volatile RejectedExecutionHandler handler;
final void reject(Runnable command) {
handler.rejectedExecution(command, this);
}
handler即为拒绝策略的处理器,是线程池的一个实例变量,通常在线程池创建时传入,JKD中有四种实现:
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();
}
}
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) {
}
}
}
总结:
清理一个即将销毁的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。
SingleThreadExecutor是使用单个工作线程的线程池。
线程池的参数配置和具体选型是和业务场景紧密相关的,好多公司都是基于业务场景,在JDK原生的ThreadPoolExecutor封装定制化的线程池。