一.ThreadpoolExecutor 的设计
1. 我们可以把需要执行的任务理解为生成者,线程池理解为消费者,来看线程池的设计.
(1)需要调度的任务在run()方法中编写具体逻辑,然后放入一个阻塞队列中,被线程池调用时执行,未被调用时处于阻塞状态。
(2)要是队列满了怎么办?表示核心线程不足,处理不过来,可以找临时线程帮忙。
(3)临时线程也不能无限制的创建所以需要设定最大线程数。
(4)当队列满了,也达到了最大线程数,就需要策略来进行处理(比如抛异常拒绝继续接收)
(5)当队列空了,或者临时线程拿不到任务了,证明已经不需要帮忙了。所以我们需要设定存活时间,在过期时间内还没有任务,临时线程就销毁。
(6)线程池中线程的创建可以通过一个工厂方法。
2(ThreadpoolExecutor 有多个重载的构造方法,它最完整的构造方法中每个参数如)
corePoolSize | 核心线程数量 |
maximumPoolSize | 最大线程数 |
keepAliveTime | 超时时间,超出核心线程数量以外的线程空余存活时间 |
unit | 存活时间单位 |
workQueue | 保存执行任务的队列 |
threadFactory | 创建新线程使用的工厂 |
handler | 当任务无法执行的时候的处理方式 |
3.任务缓存队列及排队策略
即 workQueue,它用来存放等待执行的任务。 workQueue 的类型为 BlockingQueue,通常可以取下面三种类型:
ArrayBlockingQueue | 基于数组的先进先出队列,此队列创建时必须指定大小 |
LinkedBlockingQueue | 基于链表的先进先出队列,如果创建时没有指定此队列大小,则默 认为 Integer.MAX_VALUE |
SynchronousQueue | 这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个 线程来执行新来的任务 |
二、ThreadpoolExecutor的底层实现
1.execute(Runnable command)
ctl它是一个原子类,他用到了位运算 一个 int 数值是 32 个 bit 位,这里采用高 3 位来保存运行状态,低 29 位来保存线程数量。
- 当前池中线程比核心数少,新建一个线程执行任务
- 核心池已满,但任务队列未满,添加到队列中
- 核心池已满,队列已满,试着创建一个临时线程, 如果创建新线程失败了,说明线程池被关闭或者线程池完全满了,拒绝任务
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();
}
//2.核心池已满,但任务队列未满,添加到队列中
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.核心池已满,队列已满,试着创建一个新线程
else if (!addWorker(command, false))
//如果创建新线程失败了,说明线程池被关闭或者线程池完全满了,拒绝任务
reject(command);
}
1.1 addWorker()创建核心线程或临时作线程方法做的事情:
(1)判断线程池状态
if (rs >= SHUTDOWN &&! (rs == SHUTDOWN && firstTask == null &&! workQueue.isEmpty()))
return false;
(2) 创建线程之前,要判断是否超过设定的阈值. 如果是创建核心线程比较与corePoolSize 的大小,如果是创建临时线程比较与maximumPoolSize
if (wc >= CAPACITY ||wc >= (core ? corePoolSize : maximumPoolSize))
return false;
(3)用用循环 CAS 操作来将线程数加 1 ,失败则重试(go to语句)
if (compareAndIncrementWorkerCount(c))
break retry;
(4)新建一个线程(创建过程加锁)并启动。
boolean workerStarted = false; //工作线程是否启动的标识
boolean workerAdded = false; //工作线程是否已经添加成功的标识
Worker w = null;
try {
//构建一个 Worker,这个 worker 是什么呢?我们可以看到构造方法里面传入了一个 Runnable 对象
w = new Worker(firstTask);
final Thread t = w.thread; //从 worker 对象中取出线程
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
//这里有个重入锁,避免并发问题
mainLock.lock();
try {
int rs = runStateOf(ctl.get());
//只有当前线程池是正在运行状态,[或是 SHUTDOWN 且 firstTask 为空],才能添加到 workers 集合中
if (rs < SHUTDOWN ||(rs == SHUTDOWN && firstTask == null)) {
//任务刚封装到 work 里面,还没 start,你封装的线程就是 alive,几个意思?肯定是要抛异常出去的
if (t.isAlive())
throw new IllegalThreadStateException();
workers.add(w); //将新创建的 Worker 添加到 workers 集合中
int s = workers.size();
//如果集合中的工作线程数大于最大线程数,这个最大线程数表示线程池曾经出现过的最大线程数
if (s > largestPoolSize)
largestPoolSize = s; //更新线程池出现过的最大线程数
workerAdded = true;//表示工作线程创建成功了
}
} finally {
mainLock.unlock(); //释放锁 }
if (workerAdded) {//如果 worker 添加成功
t.start();//启动线程
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w); //如果添加失败,回滚,就是递减实际工作线程数
}
return workerStarted;//返回结果
}
1.1.1 new worker(firstTask):新建工作线程
worker 实现了Runnable 接口在worker的构造函数中 getThreadFactory().newThread(this); 其中this就是woker对象,所以 t.start() 等价于new Thread (new worker).start(),可以启动线程,然后执行run方法
private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
final Thread thread; //这才是真正执行 task 的线程,从构造函数可知是由ThreadFactury 创建的
Runnable firstTask; //这就是需要执行的 task
volatile long completedTasks; //完成的任务数,用于线程池统计
Worker(Runnable firstTask) {
setState(-1); //初始状态 -1,防止在调用 runWorker(),也就是真正执行 task前中断 thread。
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
public void run() {runWorker(this);}
}
1.1.2 runWorker(this): 运行工作线程
- 如果 task 不为空,则开始执行 task,如果 task 为空,则通过 getTask()再去取任务,并赋值给 task,如果取到的 Runnable 不为空,执行该任务
- 执行完毕后,通过 while 循环继续 getTask()取任务 。在这里实现了 [线程复用]
- 如果 getTask()取到的任务依然是空,那么整个 runWorker()方法执行完毕
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
//unlock,表示当前 worker 线程允许中断,因为 new Worker 默认的 state=-1,此处是调用Worker 类的tryRelease()方法,将 state 置为 0,而 interruptIfStarted()中只有 state>=0 才允许调用中断
w.unlock();
boolean completedAbruptly = true;
try {
//注意这个 while 循环,在这里实现了 [线程复用] 如果 task 为空,则通过getTask 来获取任务
while (task != null || (task = getTask()) != null) {
//上锁,不是为了防止并发执行任务,为了在 shutdown()时不终止正在运行的 worker
w.lock();
//线程池为 stop 状态时不接受新任务,不执行已经加入任务队列的任务,还中断正在执行的任务所以对于 stop 状态以上是要中断线程的
//(Thread.interrupted() &&runStateAtLeast(ctl.get(), STOP)确保线程中断标志位为 true 且是 stop 状态以上,接着清除了中断标志
//!wt.isInterrupted()则再一次检查保证线程需要设置中断标志位
if ((runStateAtLeast(ctl.get(), STOP) ||(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted())
wt.interrupt();
try {
//这里默认是没有实现的,在一些特定的场景中我们可以自己继承 ThreadpoolExecutor 自己重写
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run(); //执行任务中的 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,需要再通过 getTask()取) + 记录该 Worker 完成任务数量 + 解锁
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
//1.将入参 worker 从数组 workers 里删除掉;
//2.根据布尔值 allowCoreThreadTimeOut 来决定是否补充新的 Worker 进数组workers
}
}
1.1.2.1 geTask()
- 判断线程池状态,对于shutdown且 workQueue为空或stop的workerCount-1,并且返回 null
- 比较前线程池中的线程数量大于核心线程数量。对于对于超过核心线程数量的这些线程(临时线程),需要进行超时控制。(ps: allowCoreThreadTimeOut 默认为false,核心线程默认不允许进行超时)
- 超时控制就是在线程从工作队列 poll 任务时,加上了超时限制,如果线程在 keepAliveTime 的时间内 poll 不到任务,那我就认为这条线程没事做, 可以干掉了。
- 如果拿到的任务不为空,则直接返回给 worker 进行处理
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 对线程池状态的判断,两种情况会 workerCount-1,并且返回 null
// 线程池状态为 shutdown,且 workQueue为空(shutdown 状态的线程池还是要执行workQueue 中剩余的任务的)
/线程池状态为stop(shutdownNow()会导致变成 STOP)(此时不用考虑 workQueue的情况)
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;//返回 null,则当前 worker 线程会退出
}
int wc = workerCountOf(c);
// timed 变量用于判断是否需要进行超时控制。
// allowCoreThreadTimeOut 默认是 false,也就是核心线程不允许进行超时;
// wc > corePoolSize,表示当前线程池中的线程数量大于核心线程数量;对于超过核心线程数量的这些线程,需要进行超时控制
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
//1. 线程数量超过 maximumPoolSize 可能是线程池在运行时被调用了 setMaximumPoolSize()被改变了大小,否则已经 addWorker()成功不会超过 maximumPoolSize
//2. timed && timedOut 如果为 true,表示当前操作需要进行超时控制,并且上次从阻塞队列中获取任务发生了超时.其实就是体现了空闲线程的存活时间
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
根据 timed 来判断,如果为 true,则通过阻塞队列 poll 方法进行超时控制,如果在keepaliveTime 时间内没有获取到任务,则返回 null.否则通过 take 方法阻塞式获取队列中的任务
Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)//如果拿到的任务不为空,则直接返回给 worker 进行处理
return r;
timedOut = true;//如果 r==null,说明已经超时了,设置 timedOut=true,在下次自旋的时候进行回收
} catch (InterruptedException retry) {
// 如果获取任务时当前线程发生了中断,则设置 timedOut 为false 并返回循环重试
timedOut = false;
}
}
}
1.1.3 processWorkerExit :线程销毁
getTask 方法返回 null 时,在 runWorker 方法中会跳出 while 循环在 finally 中会调用 processWorkerExit,来销毁工作线 程。
1.2 线程启动失败,回滚: addWorkerFailed(w);
- 如果 worker 已经构造好了,则从 workers 集合中移除这个 worker
- 原子递减核心线程数(因为在 addWorker 方法中先做了原子增加)
- 尝试结束线程池
2.reject拒绝策略
AbortPolicy | 直接抛出异常,默认策略 |
CallerRunsPolicy | 用调用者所在的线程来执行任务 |
DiscardOldestPolicy | 丢弃阻塞队列中靠最前的任务,并执行当前任务 |
DiscardPolicy | 直接丢弃任务; |
当然也可以根据应用场景实现 RejectedExecutionHandler 接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务