在文章开始之前,我们先要明白为啥要有线程池这么个东西。线程是一种稀缺资源,若不加以限制,不仅会占用大量资源,还会影响系统的稳定性。而线程池可以对线程的创建与停止、线程数量等等因素加以控制,使得线程在一种可控的范围内运行,在保证系统稳定运行的同时,还使得性能调优更加方便。另外,每次请求到来时,由于线程的创建已经完成,所以可以直接执行任务,减少了每次创建线程、销毁线程的开销,提高了响应速度。
OK,下面我们就进入正戏,源码…
ThreadPoolExecutor 核心继承关系,成员变量及主要构造函数:
public class ThreadPoolExecutor extends AbstractExecutorService {
// ctl 线程池状态控制字段,由两部分组成:
// int ctlOf(int rs, int wc) { return rs | wc; }
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 1:workerCount wc 工作线程数,我们限制 workerCount 最大到(2^29)-1,大概 5 亿个线程
private static final int COUNT_BITS = Integer.SIZE - 3;// 29
private static final int CAPACITY = (1 << COUNT_BITS) - 1;// =(2^29)-1=536870911
// 2:runState rs 线程池的状态,提供了生命周期的控制,源码中有很多关于状态的校验,状态枚举如下:
private static final int RUNNING = -1 << COUNT_BITS;//536870912
private static final int SHUTDOWN = 0 << COUNT_BITS;//0
private static final int STOP = 1 << COUNT_BITS;//536870912
private static final int TIDYING = 2 << COUNT_BITS;//1073741824
private static final int TERMINATED = 3 << COUNT_BITS;//1610612736
// Worker 线程池中任务执行的最小单元
private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
...}
// 任务队列,阻塞队列,来保持线程的存活周期
// 注:这里使用阻塞队列而不是普通容器,是为了worker在取任务时借助它的线程调度能力
// 比如当线程池中的线程空闲下来了,它就会在队列的另一端等待外部线程投递任务
// 这里注意,因为将外部线程的任务放入任务队列调用的是offer方法,所以入队时不会出现外部线程阻塞
private final BlockingQueue<Runnable> workQueue;
// 工作线程集合,包含线程池中所有的工作线程
private final HashSet<Worker> workers = new HashSet<Worker>();
// 锁,大多数情况下是控制对 workers 的访问权限(如将新worker加入)
private final ReentrantLock mainLock = new ReentrantLock();
private final Condition termination = mainLock.newCondition();
// 已完成任务的计数
volatile long completedTasks;
// 线程池最大容量
private int largestPoolSize;
// 已经完成的任务数
private long completedTaskCount;
//-----------------------------用户可控属性(volatile)-----------------------------
// 可以使用 threadFactory 创建 thread
// 创建失败一般不抛出异常,只有在 OutOfMemoryError 时候才会
private volatile ThreadFactory threadFactory;
// 线程空闲的最大时间
private volatile long keepAliveTime;
// coreSize,allowCoreThreadTimeOut决定是否回收
private volatile int corePoolSize;
// maxSize,除核心线程外,空闲就会回收
private volatile int maximumPoolSize;
// 饱和或者运行中拒绝任务的 handler 处理类
private volatile RejectedExecutionHandler handler;
// 默认的拒绝策略
private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();
// 设置 true 的话,核心线程空闲 keepAliveTime 时间后,也会被回收
// 需要调用allowCoreThreadTimeOut方法进行设置,默认false
private volatile boolean allowCoreThreadTimeOut;
//-------------------------------构造函数----------------------------------------
// 构造函数的作用就是设置上面的volatile变量们
// 注:前五参数个必有,theadFactory与rejected可以没有
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
...}
}
定义 execute 方法来执行任务,入参是 Runnable,无出参
public interface Executor {
void execute(Runnable command);
}
Executor 的功能太弱,ExecutorService 丰富了对任务的执行和管理的功能,主要代码如下:
public interface ExecutorService extends Executor {
// 提交有返回值的任务,使用 get 方法可以阻塞等待任务的执行结果返回
<T> Future<T> submit(Callable<T> task);
// 提交没有返回值的任务,如果使用 get 方法的话,任务执行完之后得到的是 null 值
Future<?> submit(Runnable task);
// 给定任务集合,返回已经执行完成的 Future 集合,每个返回的 Future 都是 isDone = true 的状态
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
// 给定任务中有一个执行成功就返回,如果抛异常,其余未完成的任务将被取消
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException;
// 关闭,不会接受新的任务,也不会等待未完成的任务
// 如果需要等待未完成的任务,可以使用 awaitTermination 方法
void shutdown();
// 在超时时间内,等待剩余的任务终止
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
// executor 是否已经关闭了,返回值 true 表示已关闭
boolean isShutdown();
// 所有的任务是否都已经终止,是的话,返回 true
boolean isTerminated();
}
public abstract class AbstractExecutorService implements ExecutorService {
// 将Callabbe,想要返回值的Runnable转化成Cunnable
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
// FutureTask(Runnable,T) -> RunnableFuture -> Runnable
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
// submit调用的还是execute
// 只不过将Callable,要返回值的Runnable提前转化成了Runnable
// 提交无返回值的任务
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
// ftask 其实是 FutureTask
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
// 提交有返回值的任务
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
// ftask 其实是 FutureTask
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
}
线程池中任务执行的最小单元,同它的名字一样是执行任务的工具人。
由于Worker实现了Runnable,所以Worker是本质是一个线程任务。那么请先考虑下面三个问题:
为什么Worker要实现Runnable,而不是创建线程时直接用 firstTask?
答:一句话,为了线程复用。说直白点就是当线程new出来之后,它的Runnable就不能变了,所以如果直接拿某一个任务去创建线程,那么它就不能再执行别的新任务了,就无法做到复用。
那实际要执行的任务怎么办,放在那里?
答:放置的位置有两个(这段逻辑可以在runWorker方法中看到):
那要是创建的线程多了,好多线程都没有任务空闲下来了怎么办?
答:若一个线程迟迟等不到任务执行就会被回收,具体回收策略在 getTask 方法中可以看到
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
// 属于当前worker的执行任务的线程
final Thread thread;
// 实际需要执行的任务。顾名思义只保存第一个执行的任务,第一个任务执行完后=null
Runnable firstTask;
Worker(Runnable firstTask) {
// 将AQS的状态设置为-1
// 从后面的isLocked方法可以看到,state!=0 表示已经被加锁
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
// !!!为了线程的复用,Worker本身实现了 Runnable,并且把自己作为任务传递给 thread。非常巧妙的设计!
this.thread = getThreadFactory().newThread(this);
}
// Thread的启动方法start实际调用的就是run,而这里实际有两个run:
// 1.Worker 本身是 Runnable,run 方法是 Worker 执行的入口
// 2.runWorker 是外部的方法,会调用firstTask的run方法
public void run() {
runWorker(this);
}
}
另外,Worker 本身也实现了 AQS,所以其本身也是一个锁,其在执行任务的时候,会锁住自己,任务执行完成之后,会释放自己,保证了在一个线程执行任务时再被丢入别的任务。相关方法如下:
public void lock() {
acquire(1); }
public boolean tryLock() {
return tryAcquire(1); }
public void unlock() {
release(1); }
public boolean isLocked() {
return isHeldExclusively(); // Lock methods
// 尝试加锁,CAS 赋值为 1,表示锁住
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
// 尝试释放锁,释放锁没有 CAS 校验,可以任意的释放锁
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
// 0 代表没有锁住,否则代表锁住(-1,1)
protected boolean isHeldExclusively() {
return getState() != 0;
}
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
入口,选择执行策略,分为以下三种情况:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get(); // 获取 ctl
// 情况一:工作的线程小于核心线程数,创建新的线程,成功返回,失败不抛异常
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
// 由于 addWorker -> runWorker -> getTask,所以线程池状态可能发生变化
c = ctl.get();
}
// 情况二:工作的线程大于等于核心线程数且任务队列没满
// 注:isRunning是校验线程池状态是否正常。另外,offer不阻塞而是返回t/f
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// 如果线程池状态异常 尝试从队列中移除任务,可以移除的话就拒绝掉任务
if (!isRunning(recheck) && remove(command))
reject(command);
// 发现可运行的线程数是 0,就初始化一个线程,这里是个极限情况,入队的时候,突然发现可用线程都被回收了
else if (workerCountOf(recheck) == 0)
// Runnable是空的,不会影响新增线程,但是线程在 start 的时候不会运行
// Thread.run() 里面有判断
addWorker(null, false);
}
// 情况三:队列满了,开启线程到 maxSize,如果失败直接拒绝(这段逻辑可以在addWorker方法中看到)
else if (!addWorker(command, false))
reject(command);
}
创建woker,返回worker中的线程是否成功启动。大致过程如下:
// firstTask 不为空可以直接执行,为空执行不了,Thread.run()方法有判断,Runnable为空不执行
// core 为 true 表示线程最大新增个数是 coresize,false 表示最大新增个数是 maxsize
private boolean addWorker(Runnable firstTask, boolean core) {
// break retry 跳到retry处,且不再进入循环
// continue retry 跳到retry处,且再次进入循环
retry:
--------------------------------------------------------------------------------------------------------------
// 1.先是各种状态的校验
for (;;) {
int c = ctl.get();
int rs = runStateOf(c); // 获取线程池状态
// 1.1 校验线程池状态,rs>=0:SHUTDOWN,STOP,TIDYING,TERMINALED
if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c); // 得到当前工作线程数,即worker数
// 1.2 校验工作中的线程数大于等于容量,或者大于等于 coreSize or maxSize
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize)) // 如果core为true就判断是否大于coreSize,否则判断maxSize
return false;
// CAS修改workerCount(+1)
if (compareAndIncrementWorkerCount(c))
// break 结束 retry 的 for 循环
break retry;
// 到这里可能是CAS失败了,重新获取 ctl
c = ctl.get();
// 如果线程池状态被更改
if (runStateOf(c) != rs)
continue retry; // 跳转到retry位置,重新判断
// else CAS failed due to workerCount change; retry inner loop
}
}
--------------------------------------------------------------------------------------------------------------
// 2.创建worker
// 2.1 创建标识变量
boolean workerStarted = false; // woker启动标识
boolean workerAdded = false; // woker成功加入worker容器标识
Worker w = null;
try {
// 2.2 构造worker。在worker的构造函数中会调用newThread方法创建一个Thread
// 注:由于Worker也实现了Runnable,所以在创建线程的时候是newThread(this)。这是一个巧妙的设计
w = new Worker(firstTask);
final Thread t = w.thread; // 获取worker中的线程
// 2.3 将worker加入到worker容器(Set)
if (t != null) {
final ReentrantLock mainLock = this.mainLock; // 这个mainLock是一个成员变量,作用是控制对worker的操作
// 加锁是因为,可能有多个线程同时要将worker放入worker容器
mainLock.lock();
try {
// 获取到线程池状态rs
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN || // 如果线程池状态是 RUNNING
(rs == SHUTDOWN && firstTask == null)) {
// 线程池是SHUTDOWN且要执行的任务为null
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
// 将当前woker加入到 HashSet workers 中
workers.add(w);
int s = workers.size(); // 获取到 workers 的大小,即现在有几个worker
// 如果worker数已经大于了最大线程池容量
if (s > largestPoolSize)
largestPoolSize = s; // 将largestPoolSize设置为worker现在的书香
workerAdded = true; // 添加标志设置为成功
}
} finally {
mainLock.unlock(); // 解锁
}
--------------------------------------------------------------------------------------------------------------
// 3.启动如果woker中的线程。前提是worker已经添加成功
if (workerAdded) {
// 启动刚创建线程:Thread#start -> Worker#run -> runWorker
t.start();
workerStarted = true; // 线程启动标志置为true
}
}
} finally {
// 如果线程启动失败
if (! workerStarted)
addWorkerFailed(w);
}
// 返回线程是否启动成功
return workerStarted;
}
首先获取任务,然后让worker去执行任务。该方法大致逻辑如下:
这里再注意一点,while 目的是维持当前线程持续执行任务,但线程如果迟迟拿不到 task(getTask方法中会阻塞等待)就会退出循环,即线程生命结束被回收。
final void runWorker(Worker w) {
Thread wt = Thread.currentThread(); // 获取当前线程
Runnable task = w.firstTask; // 尝试获取创建worker时的firstTask
w.firstTask = null; // 从这可以看出,只要firstTask执行过一次,就会一直被置为null
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
// 1.获取任务:如果firstTask已经被执行过了,就从任务队列中获取
// 注:通过while维持了线程的存活,并不断获取任务取执行。若迟迟拿不到任务,就会退出while结束线程
while (task != null || (task = getTask()) != null) {
// 2.锁住 worker,防止worker在执行任务时被丢入另一个任务
w.lock();
// 3.判断线程池若处于 stop 中,但线程没有到达中断状态,帮助线程中断
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
// 4.执行 before 钩子函数
beforeExecute(wt, task);
Throwable thrown = null;
try {
// 5.同步执行任务
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 {
// 6.执行 after 钩子函数
// 如果这里抛出异常,会覆盖 catch 的异常,所以这里异常最好不要抛出来
afterExecute(task, thrown);
}
} finally {
// 7.任务执行完成,删除任务,并计算解锁
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
// 做一些抛出异常的善后工作
processWorkerExit(w, completedAbruptly);
}
}
从阻塞队列中获取任务,若阻塞等待后还没取到任务就会返回null,从而使当前线程在runWorker方法中退出while循环被回收,因为没事干了留着还浪费资源。具体回收策略在源码中,该方法大致流程如下:
这里再强调一次,核心线程与非核心线程只是概念上的区别,在代码中大家都一样,都是普通Thread。
private Runnable getTask() {
// 标识是否超时
// 默认false,但如果下面自旋中 poll 在 keepAliveTime(线程存活时间) 没等到任务,就会将timedOut置为true
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c); // 获取线程池状态
// 1.第一次判断是否回当前收线程
// 线程池关闭 && 队列为空,不需要在运行了,直接返回null
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount(); // workerCount--
return null;
}
int wc = workerCountOf(c); // 获取worker个数
// timed的作用是决定在阻塞队列中等任务时用 poll 还是 take
// timed = 核心线程可以被灭亡(默认false) || 运行的线程数大于 coreSize
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
// 2. 第二次判断是否回收当前线程。组合后分为4种情况
if ((wc > maximumPoolSize || (timed && timedOut)) // woker大于线程池最大数量 || (timed && 当前线程已经超时)
&& (wc > 1 || workQueue.isEmpty())) {
// woker大于1 || 任务队列为空
// 通过CAS使workerCount--
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
// 3.从阻塞队列中获取任务。timed决定了是使用 poll 还是 take
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : // poll,超时了就返回
workQueue.take(); // take,任务队列中没任务会阻塞等待
// 如果在队列拿到了任务就返回
if (r != null)
return r;
// 没拿到就将超时timedOut设置为true,表示此时队列没有数据
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
在文章的最后,我们再对 ThreadPoolExecutor 的关键信息做一些总结:
线程池解决两个问题:
线程池容量相关参数:
coreSize:当新任务提交时,发现运行的线程数小于 coreSize,一个新的线程将被创建,即使这时候其它工作线程是空闲的,可以通过 getCorePoolSize 方法获得 coreSize
maxSize: 当任务提交时,coreSize < 运行线程数 <= maxSize,但队列没有满时,任务提交到队列中,如果队列满了,在 maxSize 允许的范围内新建线程;
一般来说,coreSize 和 maxSize 在线程池初始化时就已经设定了,但我们也可以通过 setCorePoolSize、setMaximumPoolSize 方法动态的修改这两个值;
Keep-alive times 参数:
线程池新建时的队列选择有很多,比如:
拒绝策略:在 Executor 已经关闭或对最大线程和最大队列都使用饱和时,可以使用 RejectedExecutionHandler 类进行异常捕捉。有如下四种处理策略:
ExecutorService 使用线程池中的线程执行提交的任务,线程池我们可以使用 Executors 进行配置.Executors 为常用的场景设定了可直接初始化线程池的方法,比如:
另外,线程池提供了很多可供扩展的钩子函数,比如有: