背景:
线程池是一种池化思想,线程池的引入是为了更方便、更高效、合理地使用线程。体现在三个方面:
1.线程的创建和销毁过程消耗CPU和内存资源,反复使用线程可以省出这些资源;
2.线程的创建需要时间,使用线程池中已经创建好的线程,可以缩短服务端的响应时间。
3.线程是比较重的开销,需要通过线程池对线程的数量进行管控。
业务代码中涉及到使用线程的场景,应当通过线程池技术来实现。编码规范中也要求开发过程中使用线程池,不使用new Thread等创建线程。因为野线程不好管控,可能造成OOM导致DOS。
一、线程池基础
1.线程池的参数
从构造函数看,线程池的参数一共有以下几个:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
(1) corePoolSize 核心线程数;
(2) maximumPoolSize 最大线程数;
(3) keepAliveTime 非核心线程的最大空闲时间;
(4) unit 时间单位;
(5) workQueue 阻塞队列;
(6) threadFactory 线程工厂;
(7) handler 拒绝策略;
线程池中的线程是通过线程工厂创建的;线程池创建之初,线程的数量为0(特别进行设置除外);随着任务的到来,线程池会创建线程来执行任务,直到线程数量达到核心线程数(哪怕有空闲线程);之后到达的任务如果不能被核心线程及时处理,就会进入阻塞队列中排队等候;阻塞队列满后,线程池会继续增加线程来执行任务;
当线程池中线程的数量达到最大线程数时,无法再创建线程,此时到达的任务,会调用拒绝策略来处理。当非核心线程的空窗时间达到空闲时间时,会回收该线程。allowCoreThreadTimeOut
这个参数(默认为 false)被设置为 true后, 核心线程也会有超时时间。
2.线程工厂
JUC中定义了一个线程工厂接口ThreadFactory,其中声明了唯一一个方法newThread
:接收Runnable类型的任务,返回一个Thread对象。
public interface ThreadFactory {
Thread newThread(Runnable r);
}
通过实现ThreadFactory接口以及重写newThread
方法可以实现自定义线程工厂。一般不推荐接使用默认的线程工厂,因为不方便后台日志的过滤和定位。
在Executors中定义了一个DefaultThreadFactory类:根据Runnable对象创建线程池对象,同时设置线程的线程池组、线程池名字、优先级等参数,生成的名字呈pool-1-thread-1
形式。
3.拒绝策略
在java并发包中定义了一个RejectedExecutionHandler接口类:
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
实现该接口类并重写rejectedExecution方法可以自定义拒绝策略,其中 r表示待处理的任务,executor表示线程池对象。
ThreadPoolExecutor类中给我们提供了四种拒绝策略:
(1)AbortPolicy:默认策略,丢弃任务,并抛出异常
(2)CallerRunsPolicy:使用当前线程来执行提交给线程池的任务;
(3)DiscardOldestPolicy:丢弃队头的任务,并将当前任务加入队列;
(4)DiscardPolicy:直接丢弃任务,不抛出异常;
public static class DiscardPolicy implements RejectedExecutionHandler {
public DiscardPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
// rejectedExecution方法体为空,表示什么都不干-直接丢弃任务。
}
}
public static class AbortPolicy implements RejectedExecutionHandler {
public AbortPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
// 拒绝任务,直接抛出异常
throw new RejectedExecutionException(
"Task " + r.toString() +" rejected from " +e.toString());
}
}
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
public DiscardOldestPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll(); // 删除阻塞队列中对头的任务
e.execute(r); // 向线程池提交任务r
}
}
}
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public CallerRunsPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run(); // 直接调用run()而不是start(), 表示当前线程执行执行任务r(而不是线程池中的线程)
}
}
}
4.阻塞队列
在介绍阻塞队列前,先简单介绍一下Queue这个接口。
4.1 Queue
Queue继承自Collection,新增了如下几个方法:
public interface Queue<E> extends Collection<E> {
boolean add(E var1);
boolean offer(E var1);
E remove();
E poll();
E element();
E peek();
}
可以大致分成2类:
抛出异常类:
- add(E): 向队列里添加元素,如果队列未满–返回true;否则–抛出异常;
- remove(): 取出队头元素,如果队列为空–抛出异常;否则–返回该元素;
- element(): 查询队头元素,如果队列为空–抛出异常吗;否则–返回元素;
返回“null值”类
- offer(E): 向队列里添加元素,如果队列已满–返回fasle;添加成功–返回true;
- poll(): 从队列里取出元素,如果队列为空–返回null;否则返回E;
- peek(): 查询队列元素,如果队列为空–返回null;否则返回E;
4.2 BlockingQueue
BlockingQueue继承子Queue, 新增了“加”和“取”的阻塞方法。
void put(E var1) throws InterruptedException;
E take() throws InterruptedException;
put是阻塞性地添加元素:如果队列未满,插入数据,返回true;否则,则阻塞线程–等待队列不满时插入元素。
take是阻塞性地取出元素:如果队列不为空,返回E;否则阻塞等待–直到队列不为空。
另外,重载了两个方法,使之拥有延时的功能:
boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException;
E poll(long timeout, TimeUnit unit) throws InterruptedException;
最多等待timeout
时间,如果超时直接返回false或null。
BlockingQueue存在多个实现类,常常和线程池联系的类有:ArrrayBlockingQueue 是基于数组的有界队列,使用时需要提前设置队列长度;LinkedBlockingQueue基于链表的无界队列,也可以设置为有界队列;
SynousQueue是一个比较特殊的队列,没有存储空间,生产者插入一个节点时会阻塞,一直阻塞到有消费者来消费这个节点;反之也是一样。
这部分内容在JAVASE-3 多线程体系4-阻塞队列有详细的介绍。
5.四种常见的线程池
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService(
new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
如上述源码:
1.CachedThreadPool:核心线程数是0,最大线程数为Integer的最大值,阻塞队列使用的是SynchronousQueue;
2.FixedThreadPool:线程池的核心线程数和最大线程数一致,阻塞队列使用的是LinkedBlockingQueue无界队列;
3.SingleThreadExecutor:只有一个线程,阻塞队列使用的是LinkedBlockingQueue无界队列;
4.ScheduledThreadPool:该线程池用于执行定时任务相关的任务。
Executors这个类提供了4种快速创建线程池的方法,不过由于参数的局限性,不建议在实际生产环境中使用。因为上述4种线程池要么线程数量没有上限,要么阻塞队列无界,容易导致OOM。
二、线程池原理
知其然,之后就是知其所以然。上面介绍了一些线程池的性质,这里会结合源码详细介绍这些性质的来源和本质。介绍的过程中重在理解线程池的实现原理,不会拘泥于源码而面面俱到(有些场景会做省略),以求文章的精简。
1.线程池的继承体系
看源码时,一般先跳出来看大概,然后钻进去研究细节。可以先看一下线程池的主干继承体系。
Executor是基类接口,定义了一个execute接口,接收Runnable;
public interface Executor {
void execute(Runnable var1);
}
ExecutorService接口对Executor接口进行了增强: 引入了shutdown()
和submit()
方法;
public interface ExecutorService extends Executor {
void shutdown();
List<Runnable> shutdownNow();
boolean isShutdown();
boolean isTerminated();
<T> Future<T> submit(Callable<T> var1);
<T> Future<T> submit(Runnable var1, T var2);
Future<?> submit(Runnable var1);
//次数省略invoke系列以及awaitTermination相关方法
}
抽象类AbstractExecutorService对ExecutorService的抽象方法提供了基本的实现,主要对**submit
**和invoke
系列方法进行了实现,本节关注的重点的submit上,涉及部分代码如下。
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
对Callable和Runnable两种任务做了一层适配, 细节请参考JAVASE-3 多线程体系5-FutureTask源码分析,然后调用线程池的execute()方法来执行任务。
在此基础上,ThreadPoolExector实现了剩下的抽象接口并增强了线程池的功能。比如实现了execute
和shutdown
等抽象方法,增加了线程池的参数、状态,并提供了与之对应的构造方法,提供了一个可以直接使用的线程池类。
2.线程池的状态
JUC中几乎任何一个类中都离不开状态变量,队列,CAS;线程池也不例外。ThreadPoolExector中定义了5种线程的状态:
private static final int COUNT_BITS = Integer.SIZE - 3; // 29
private static final int RUNNING = -1 << COUNT_BITS; // -1<<29
private static final int SHUTDOWN = 0 << COUNT_BITS; // 0
private static final int STOP = 1 << COUNT_BITS; // 1<<29
private static final int TIDYING = 2 << COUNT_BITS; // 2<<29
private static final int TERMINATED = 3 << COUNT_BITS; // 3<<29
RUNNING表示线程池处于运行状态,这个阶段线程可以接收任务、 执行任务,线程池创建之后默认处于该状态。
SHUTDOWN状态的线程不再接收任务,但是正在运行的任务以及队列里的任务依然会正常执行,调用shutdown()方法后,线程池处于该状态。
STOP状态的线程,不再接收任务,且直接丢弃对列中的任务并尝试中断正在执行的任务,调用shutdownNow()会使线程处于该状态
当线程池中任务为空时,线程会转入TIDYING 状态;
TIDYING状态的线程池会会执行钩子函数terminated(),进入TERMINATED 状态,彻底关闭线程池。
3.向线程池提交任务—submit 和 execute
submit
方法在AbstractExecutorService类中定义:
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
这里submit
可以接收三种参数,本质上只有一种。无论是Callable还是Runnabel最后都会被转换成FutureTask, 然后传参给execute()
。至于怎么构造FutureTask以及这么做的原因,请参考JAVASE-3 多线程体系5-FutureTask源码分析。
接下来调用逻辑就进入了 execute(Runnabel)
。这个方法也是public,即execute
和submit
都是对外开放给用户使用的接口,提供给开发人员调用–以提交执行任务。
execute
方法是在Executor中引入,在ThreadPoolExecutor中实现,具体实现如下:
public void execute(Runnable command) {
if (command == null) {
throw new NullPointerException();
}
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true)) {
return;
}
c = ctl.get();
}
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);
}
} else if (!addWorker(command, false)) {
reject(command);
}
}
首先,提交给execute
方法的任务不能为空,否则抛出异常。
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true)) {
return;
}
c = ctl.get();
}
先判断当前线程池的线程数是否小于核心线程数,如果小于,直接调用addWorker(command, true)
添加一个线程来运行command。添加成功,则直接退出逻辑。
如果添加失败,可能有以下原因:
(1) 线程池不处于RUNNING状态
(2) 状态和线程数量在调用addWorker(command, true)
过程中发生了变化,导致不满足添加核心线程的条件。
此时,需要重新获取线程池的属性:int c = ctl.get();
当不能添加核心线程时,会走到下面的逻辑:
if (isRunning(c) && workQueue.offer(command)) { // IF_1
// do sth
} else if (!addWorker(command, false)) { // IF_2
reject(command);
}
线程池接收任务的前提条件是线程处于RUNNING状态;否则,调度会进入IF_2
,addWork(command, false)
因为command
不为空且线程池不处于RUNNING状态而失败(后面介绍),从而调用reject(command)
拒绝该任务。
=> 只有处于Running状态的线程池可以接收新的任务。
如果线程池处于RUNNING状态,接着会调用workQueue.offer(command)
把该任务添加到阻塞队列中。
如果添加失败,表示阻塞队列已满,此时调度进入IF_2
:调用addWorker(command, false)
添加核心线程之外的线程,如果添加成功,使用新创建的线程运行该任务,如果线程处理已达到最大线程数,而添加失败,调用reject(command)
拒绝该任务。
如果添加到阻塞队列成功:
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command)) {
reject(command);
} else if (workerCountOf(recheck) == 0) {
addWorker(null, false);
}
再次判断线程池的状态(因为在将任务加入到阻塞队列的过程中,线程池可能发生了变化)。
如果已经变化,即状态不再是RUNNING, 则从阻塞队列中删除,并调用reject(command)
拒绝该任务;
否则,添加任务到阻塞队列成功;此时,做了层优化:判断线程池中线程数量是否为0,如果是则创建一个空线程,以便从阻塞队列中获取任务执行,提高效率。
接下来进入addWorker
方法:
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
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;
}
}
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 {
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive())
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;
}
addWorker
方法的逻辑大体可以分为两段:第一段以CAS的方式使线程池计数变量加1;第二段新建一个线程,并调用线程的start()
方法,启动线程。
先看第一段:
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
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加自旋实现线程计数加1。
rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())
:线程池的状态小于SHUTDOWN或者线程池的状态等于SHUTDOWN且提交的任务是空且任务队列不为空,才继续走下面的逻辑,否则直接退出最外层自旋,返回false。
即:RUNNING状态下,可以添加线程;SHUTDOWN下不允许接受任务,但是任务队列为空时可以新增线程;其他状态不允许新增线程。
接着往下是一个内部自旋,根据addWorker
方法是添加的线程是否是核心线程进行了一次判断, 判断线程池中的线程数量是否已经超限,超过会添加失败,退出该方法-返回false。
然后调用compareAndIncrementWorkerCount
来实现原子性自增,如果失败表示线程池的线程数量已经变化,需要重新获取线程池状态和线程池线程数,执行外层自旋,直到添加成功。compareAndIncrementWorkerCount
过程中,如果线程池的状态发生了变化runStateOf(c) != rs
,也需要重新执行外层循环,需要重新判断线程池当前的状态是否还满足新增线程的要求。
至此,实现了线程池计数的加1,接下来的逻辑是创建一个线程。
为了体现主体内容,这里省去了加锁和try-catch的逻辑以及非空判断:
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = new Worker(firstTask);
final Thread t = w.thread;
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive())
throw new IllegalThreadStateException();
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
if (workerAdded) {
t.start();
workerStarted = true;
}
return workerStarted;
整体上看:使用firstTask
这个任务去构造Worker对象,并将worker对象添加到workers
中;之后调用thread.start()
启动work对象中的线程对象。这个过程经历了一系列判断和校验,保证只有满足必要逻辑,才会正常创建和启动线程,否则中断操作。
rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null);
表示线程只有RUNNING状态,或者SHUTDOWN 状态且提交的任务是空的前提下,才能创建任务。
t.isAlive()
判断新创建的线程是否已经调用start()被重启了,因为一个线程不能调用两次start();
然后需要判断workers
的大小是否已经达到最大允许的线程池大小;
在上述条件都正常满足的情况下,调用t.start();
启动这个新创建的线程。
到目前为止,我们提交了一个任务firstTask
(可能为空),新建了一个线程并启用了这个线程,但是这个任务到底是怎么执行的,还不清晰,需要研究一下Worker这个类。
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
Worker(Runnable firstTask) {
setState(-1);
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
public void run() {
runWorker(this);
}
// 此处省略其他方法
}
Worker 这个类除了拥有AQS特性,还继承了Runnable,说明这个类本质上是一个可执行的任务。
构造Work对象时,将this
传参给thread
的构造方法:this.thread = getThreadFactory().newThread(this)
,因此,在addWorks
方法中调用t.start()时,线程会执行到Worker的run()
,接着调用runWorker(this);
需要注意的是:firstTask
可以为空,也可以储存任务;setState(-1);
是为了防止Work的线程还未启动就被中断。
接着看一下runWorker
方法:
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock();
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
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);
}
}
从上往下:
Thread wt = Thread.currentThread()
; wt
是当前线程,其实就是Work的thread
实例对象。
Runnable task = w.firstTask;
和w.firstTask = null;
提取出Worker的firstTask
,接着会执行该任务,且将w
的firstTask
属性置为空,是为了这个Worker对象在执行完这个任务后,还可以继续执行其其他任务。
w.unlock();
Worker既然继承了AQS,就有了AQS的特性。前面初始化Worker对象的时候,将state值设置成了-1,使得该线程无法被中断;现在已经进入了run()
方法体内,表示线程已经被start()
成功了,再次将state
设置为0,使得该线程可以被中断。
runWorker
的核心逻辑包裹在了while (task != null || (task = getTask()) != null) {// code}
这个代码块中,当任务为空以及getTask()取任务结果同时为空时,才会退出循环。为了方便阅读和理解,这里去除加锁和try-catch结构。
while (task != null || (task = getTask()) != null) {
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
beforeExecute(wt, task);
task.run();
afterExecute(task, thrown);
}
processWorkerExit(w, false);
这里有个if条件写得很繁琐,但是细节考虑很到位:
如果线程池的状态>=STOP, 此时应该中断线程。否则,需要清空线程池的中断状态;清空的这个过程中需要再次判断线程池的状态,如果线程池被设置成了>=STOP状态,还是需要中断wt。
然后直接调用任务的run()
方法;线程池还增加了两个钩子方法,给子类开放可以在任务逻辑执行前后执行代码的能力。
再次回到 while (task != null || (task = getTask()) != null)
, 如果任务为空,且task = getTask()
为空:直接退出该while
循环,然后执行processWorkerExit
方法,释放这个线程的资源。否则:这个线程会一直被用来执行任务。
接着我们看一下getTask
这个方法。
private Runnable getTask() {
boolean timedOut = false;
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
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;
}
}
}
整个方法是一个自旋逻辑,我们依次看一下可以退出自旋逻辑的执行点。
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
线程池的状态是SHUTDOWN且线程池的任务队列为空时,或者线程池的状态>=STOP, 此时线程会退出自旋。
int wc = workerCountOf(c);
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
allowCoreThreadTimeOut
表示核心线程数是否有超时机制,不设置默认为fasle。 如果线程池数量超过了核心线程数且超时了,会调用compareAndDecrementWorkerCount
,使得线程池数量减1;
另外:线程数超过了最大允许值也会调用compareAndDecrementWorkerCount
。
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
线程数没有超过核心线程池的情况下:调用workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)
:阻塞方法从任务队列中获取任务,直到超时或者成功获取任务。
否则调用:workQueue.take();
=>到此submit
和execute
的逻辑基本是到底了。
简单总结一下:
向线程池提交一个任务后,线程池会根据线程池的状态和线程的个数来决定是添加一个新的线程来执行,还是将任务加入到阻塞队列,或是直接拒绝任务。
线程池中的线程在创建之初调用start()
启动,被调度后,要么处于执行任务阶段,要么阻塞在runWorker
方法的getTask()
上,直到firtTask
被赋值(重复使用)或从阻塞队列中获取到任务,才会继续执行,此时直接调用任务的run()
方法。
当超时后,会退出runWorker
的while
循环。
4.线程池的关闭—Shutdown和 ShutdownNow
Shutdown
的作用是将线程的状态设置为SHUTDOWN:
此时线程不再接受新任务,但是正在执行的任务和阻塞队列中的任务仍然会正常执行,同时会回收空闲的线程。
ShutdownNow
的作用是将线程的状态设置为STOP:
此时线程不仅不接受新任务,阻塞队列中的任务也不会执行(会返回一个任务集合),且会中断所有正在执行的任务,由于不是所有线程都会响应中断,因此不一定会立刻停止正在运行的线程。
接下来从代码的角度分析一下:
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN);
interruptIdleWorkers();
onShutdown();
} finally {
mainLock.unlock();
}
tryTerminate();
}
checkShutdownAccess();
安全校验,使用SecurityManager检查是否有关闭线程池的权限。
advanceRunState(SHUTDOWN);
使用自旋、CAS的组合方式实现将线程池状态至少设置到SHUTDOWN。
onShutdown()
是一个钩子函数,留给继承ThreadPoolExecutor的子类重写,可以在shutdown()
被调用时做点什么。
重点在interruptIdleWorkers()
:
private void interruptIdleWorkers() {
interruptIdleWorkers(false);
}
直接调用了interruptIdleWorkers(false);
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();
}
}
逻辑比较清晰,遍历中断所有的不在工作的线程。
再来看一下ShutdownNow
:
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(STOP);
interruptWorkers();
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
不同于shutdown
方法的地方是:
将线程池的状态设置为STOP而不是SHUTDOWN;
打断线程的方法调用了interruptWorkers
;
且调用drainQueue()
抽出阻塞队列中的所有任务返回;
好奇心促使我们了解一下interruptWorkers
到底干了什么:
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers)
w.interruptIfStarted();
} finally {
mainLock.unlock();
}
}
遍历线程调用w.interruptIfStarted();
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
逻辑比较单一,确保所有线程被打算。
最后还有一个方法tryTerminate()
:
final void tryTerminate() {
for (;;) {
int c = ctl.get();
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
if (workerCountOf(c) != 0) {
interruptIdleWorkers(ONLY_ONE);
return;
}
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
terminated();
} finally {
ctl.set(ctlOf(TERMINATED, 0));
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
}
}
tryTerminate
方法由一个自旋块组成,这个自旋逻辑由3个可以退出的执行点。
if (isRunning(c) || runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty())) {
return;
}
如果线程池还是处于RUNNING状态,当然不能关闭;
runStateAtLeast(c, TIDYING)
线程池处于TIDYING状态,表示有其他线程在清理线程池,这里就可以放心退出了(下面有条语句将线程池状态设置为TIDYING);线程池处于SHUTDOWN,但是阻塞队列中还有任务时不能关闭线程池;
if (workerCountOf(c) != 0) {
interruptIdleWorkers(ONLY_ONE);
return;
}
如果线程池中,线程数量不为0,就打断一个线程,并退出。
这里是不是有个疑问:为什么只需要打断一个就可以悠闲地退出tryTerminate()
逻辑了,线程池的状态还没被设置成TERMINATED呢?
这里需要结合runWorker
、getTask
和processWorkerExit
方法来分析一下:
空闲的线程都会阻塞在getTask()
的workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)
或workQueue.take()
上,
阻塞的线程一旦被中断就会抛出中断异常,这里有一段catch捕获逻辑;继续自旋,因为线程池此时已经被SHUTDOWN或者STOP了,所以在第一个if语句会退出getTask()
并返回null
;
private Runnable getTask() {
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
// 此处省去一段无关逻辑
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
这是调用逻辑传递到了runWorker
:
final void runWorker(Worker w) {
// // 此处省去一段无关逻辑
try {
while (task != null || (task = getTask()) != null) {
此处省去一段无关逻辑
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
getTask
返回null
后,会跳出while
循环,接着执行finally
块中的processWorkerExit(w, completedAbruptly);
private void processWorkerExit(Worker w, boolean completedAbruptly) {
// 此处省去一段无关逻辑
tryTerminate();
// 此处省去一段无关逻辑
}
如果这个线程不是阻塞在getTask
上,而是正在运行的,那么等线程执行完成task.run();
执行完成,还是会执行processWorkerExit(w, completedAbruptly);
processWorkerExit
中也调用了tryTerminate()
方法。
再次回到那个疑问"为什么只需要打断一个就可以悠闲地退出tryTerminate()
逻辑了,线程池的状态还没被设置成TERMINATED呢?"
if (workerCountOf(c) != 0) {
interruptIdleWorkers(ONLY_ONE);
return;
}
此时就比较清楚了,如果workerCountOf(c) != 0
,说明至少还有一个运行的线程,中断这个线程后,这个被中断的线程就会调用tryTerminate()
,类似交接,会有一个线程跟踪tryTerminate()
的执行,依次类推…
接着看tryTerminate
方法,能执行到此处,说明线程池的状态为SHUTDOWN或TOP,且任务队列为空,线程池中线程数为0。
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
terminated();
} finally {
ctl.set(ctlOf(TERMINATED, 0));
termination.signalAll();
}
return;
}
将线程池切换到TIDYING状态,然后调用钩子函数terminated
(在ThreadPoolExecutor中执行逻辑为空)。
然后将线程池的状态设置为TERMINATED,并调用termination.signalAll()
,唤醒因调用awaitTermination
方法阻塞在termination
这个Condition上的函数。
至此,线程池源码部分主体解析完成。
有问题的同学请留言区评论或者私聊,感谢!
“独学而无友,则孤陋而寡闻。”