一、ThreadPoolExecutor解析
1、线程池的5种状态
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
(1)RUNNING:接收新任务和队列任务
(2)SHUTDOWN:不接收新任务,但是接收队列任务
(3)STOP:不接收新任务,也不接收队列任务,并且打断正在进行中的任务
(4)TIDYING:所有任务终止,待处理任务数量为0,线程转换为TIDYING,将执行terminated钩子函数
(5)TERMINATED:terminated()钩子函数执行完成
2、状态之间的转换
(1)RUNNING -> SHUTDOWN:调用shutdown()方法
(2)(RUNNING or SHUTDOWN) -> STOP:调用shutdownNow()方法
(3)SHUTDOWN -> TIDYING:队列和线程池都是空的
(4)STOP -> TIDYING:线程池为空
(5)TIDYING -> TERMINATED:钩子函数terminated()执行完成
(使用时要调用shutdown()方法,然后调用awaitTermination方法)
3、默认的4种拒绝策略
(1)CallerRunsPolicy:调用execute()方法拒绝任务
(2)AbortPolicy:抛RejectedExecutionException异常(默认的处理方式)
(3)DiscardPolicy:悄悄丢弃拒绝的任务
(4)DiscardOldestPolicy:丢弃最老的未处理的请求,然后重新执行execute()方法
二、源码分析
示例:
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(1);
executorService.execute(() -> System.out.println("hello"));
executorService.shutdown();
executorService.awaitTermination(1, TimeUnit.MINUTES);
}
Executors.newFixedThreadPool(1)只是做了一些日常的参数校验、判空操作、参数的赋值操作。
1、向线程池提交任务:execute()
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);
}
(1)当前线程池的线程数小于核心线程数时,直接添加核心线程数,把当前任务放进去
(2)如果核心线程数满了,把当前任务放到等待队列中去。放进去之后要重新再获取一次当前线程池的状态,如果获取的结果是没有运行,则把当前任务给移除,同时执行拒绝策略(为啥做这一步,因为是多线程操作,有可能在往队列里放任务时,外部已经把线程池给关闭了,所有才需要重新获取一次)。
如果获取到的线程池是正在运行且工作线程数为0,则添加非核心线程(为啥是添加非核心线程,因为允许核心线程睡眠,释放资源,默认是不允许核心线程睡眠的,临界情况,实际使用时可以忽略)
(3)如果队列也满了,则添加非核心线程数,添加失败时执行拒绝策略。
2、创建新线程:addWorker(Runnable firstTask, boolean core)
(1)此段代码检测队列是否为空。不为空才能添加线程。
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
拆分即为以下步骤
1)如果当前运行状态 >= SHUTDOWN,继续;否则(即为Running)不进行后续的判断,执行后续的代码
2)如果当前运行状态 = SHUTDOWN(不接收外部任务,但是队列任务还要继续执行),继续;否则(!= SHUTDOWN,此时即为STOP或TIDYING或TERMINATED状态),返回false,表示已经关闭线程池了,自然就不能addWorker了。
3)如果是新提交的任务(firstTask == null),继续;否则(不是新任务,老任务就不用再addWorker了),返回false。
4)如果队列是空,返回false。
(2)此段代码就是让线程数加1
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
......
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
细分即为以下步骤
1)取工作线程的数量。如果工作线程数大于等于队列的最大容量(2^29-1)或大于核心线程数(添加核心线程时)或大于最大线程数(添加非核心线程时),返回false,不能再添加线程了;否则,继续
2)使用CAS的方式把任务线程的数量加1,添加成功则跳出外层for循环(break retry);否则,继续
3)添加线程失败(为啥会添加线程数量失败,肯定是有其他线程修改了线程池的状态),重新读取线程的状态,重新判断线程的运行状态(判断线程是否调用了shutdown方法,修改了线程池的状态),如果不等于之前的线程池状态,退出循环(continue retry),不能增加线程
(3)创建线程、启动线程
经过前两步,(第一步)可以添加线程、(第二步)且线程数量已经加1,接下来就是创建线程并启动线程
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
// 打工人没有锤子肯定干不了活啊,所以 t != null
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
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 {
// 启动线程失败,则移除线程,同时线程数减1
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
细分即为以下步骤
1)new Worker(firstTask),里面有3个变量:线程、第一个任务、任务完成数量(这三个变量很好理解,打工人打工魂,打工人没有锤子<线程>怎么打工,同时办完入职手续后得知道要干的第一个活啊,同时完成的活还得记录已经完成的数量),new的时候给了工具和第一个活。
2)上锁(因为是多线程操作),添加new出来的Worker,然后启动线程。
3)如果启动线程失败,则移除Worker,同时工作线程数量减1。
3、线程的主循环Worker:runWorker(Worker w)
一直执行任务,在执行任务前和后都可以进行扩展。想要获得线程池执行时的异常,可以重写钩子函数afterExecute 或手动捕获task.run()的异常 或使用Future去get。
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
// 状态值加1
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
// 如果第一个任务不为空,则执行第一个任务
// 第一个任务为空,则从队列获取任务执行
while (task != null || (task = getTask()) != null) {
w.lock();
// 如果当前线程STOP了,则直接设置中断标志位
if ((runStateAtLeast(ctl.get(), STOP) ||
// interrupted会清除中断标记
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
// isInterrupted(不会清除中断标记),为false表示未中断,重新设置中断标记
// isInterrupted为true,已经中断
!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);
}
}
细分即为以下步骤
1)w.unlock(); // allow interrupts?new Worker的时候,线程还没有启动,没有启动怎么能中断呢,所以给了一个-1表示不能中断,而此时runWorker进来时,线程已经启动,就允许中断,而此时unlock是加1,则此时状态值为0,表示可以中断。
2)如果第一个任务不为空,继续;否则,从队列取任务,取出任务不为空,继续;取出任务为空,则表示所有的任务都执行完毕,执行退出操作
3)如果当前线程STOP了,则直接设置中断标志位;否则,手动判断线程是否中断(防止在业务代码中手动设置了中断标志位)是,则再判断当前线程是否中断了,false则整个判断就结束了,然后设置中断标志位;true(表示线程已经停止了)继续
4)Thread.interrupted(),会把中断标志位干掉,wt.isInterrupted()为false会再设置中断标志位,因为线程已经停止了<runStateAtLeast(ctl.get(), STOP))>所以需要设置中断标志位。(如果任务已经中断过一次,结果又去拿了另外一个任务过来,但是线程池已经STOP了,就需要判断是否中断了,没有就中断,已经中断了就不用再中断了)
5)执行前和执行后都有一个钩子函数,beforeExecute和afterExecute,留给子类扩展实现的
6)执行任务
7)firstTask置空,当前完成的任务数加1
有一个问题:在执行afterExecute时,里面的异常会被finally给吞掉,也就是在执行afterExecute出现异常时,后续的代码还会继续执行,需要自己手动捕获处理。 为啥在执行task = getTask()时需要捕获异常:getTask()没有任务时会阻塞等待,但是线程结束时得唤醒它的阻塞等待,需要判断是否由中断导致的线程退出。
造成completedAbruptly为true有三个地方:getTask()、beforeExecute和afterExecute出现异常。
造成completedAbruptly为false,只有getTask()为空时。
4、从队列中获取排队的任务:getTask()
5、线程结束:
private void processWorkerExit(Worker w, boolean completedAbruptly) {
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
workers.remove(w);
} finally {
mainLock.unlock();
}
tryTerminate();
int c = ctl.get();
if (runStateLessThan(c, STOP)) {
if (!completedAbruptly) {
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty())
min = 1;
if (workerCountOf(c) >= min)
return; // replacement not needed
}
addWorker(null, false);
}
}
1)如果completedAbruptly为true(由于用户异常<即beforeExecute和afterExecute中的异常>造成的工作线程死亡),工作线程数减1,否则不减,继续
2)把当前线程移除,继续
3)如果处于Running或Shutdown状态(runStateLessThan(c, STOP)),继续;否则方法执行完毕
4)如果是用户异常造成的,addWorker(null, false),新启了一个非核心线程;
if (!completedAbruptly) {
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty())
min = 1;
if (workerCountOf(c) >= min)
return; // replacement not needed
}
addWorker(null, false);
如果不是用户异常造成的(completedAbruptly为false),如果允许核心线程超时且队列不为空,而此时工作线程数大于等于1,方法直接结束,如果此时工作线程数小于1,则addWorker(null, false),启动一个非核心线程,也就是保证至少有一个线程在执行任务。