简介:
汗,又拖更了许久,终于续上了。多说无益,show code,让我们继续ThreadPoolExecutor
。
UML图:
最顶层是Executor
,核心也就是execute(Runnable command)
了。
先简单介绍一下几个核心参数:
//线程工厂,设置线程名字,方便定位线程
private volatile ThreadFactory threadFactory;
//超过最大线程数量之后执行的饱和策略
1.AbortPolicy:直接抛出异常。
2.CallerRunsPolicy:只用调用者所在线程来运行任务。
3.DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
4.DiscardPolicy:不处理,丢弃掉。
5.也可以根据自定义策略。如先持久化,记录日志或者数据库,后续补偿。
private volatile RejectedExecutionHandler handler;
//线程空闲时间,超过这个时间,线程自动退出(默认针对超出corePoolSize的线程)
private volatile long keepAliveTime;
//是否回收核心线程
private volatile boolean allowCoreThreadTimeOut;
//核心线程数量:
//小于该数量,执行任务时会创建新的线程
//默认核心线程是不会回收的,当然也可以设置成可回收
private volatile int corePoolSize;
//最大线程数量(包括线程队列里面的线程数量),达到这个线程数的时候会执行饱和策略
private volatile int maximumPoolSize;
//线程任务队列
1.ArrayBlockingQueue:基于数组的有界阻塞队列,此队列有序: FIFO(先进先出)。
2.LinkedBlockingQueue:基于链表的阻塞队列,此队列有序: 吞吐量通常要高于ArrayBlockingQueue
3.SynchronousQueue:不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用取出操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue
4.PriorityBlockingQueue:具有优先级的无限阻塞队列。
private final BlockingQueue<Runnable> workQueue;
//默认饱和策略
private static final RejectedExecutionHandler defaultHandler =
new ThreadPoolExecutor.AbortPolicy();
比较常见的参数就上面这些,其实知道了上面这些,已经足以应付不少的面试了。
什么?你想怼面试官?那好,我们继续。
要想怼面试官,源码少不了,让我们开始源码之旅:
同样的,先介绍几个阅读源码之前需要知道的参数:
//29
private static final int COUNT_BITS = Integer.SIZE - 3;
//1 << 29: 00100000000000000000000000000000
//CAPACITY:00011111111111111111111111111111
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
//-1: 11111111111111111111111111111111
private static final int RUNNING = -1 << COUNT_BITS;
//0: 00000000000000000000000000000000
private static final int SHUTDOWN = 0 << COUNT_BITS;
//1: 00000000000000000000000000000001
private static final int STOP = 1 << COUNT_BITS;
//2: 00000000000000000000000000000010
private static final int TIDYING = 2 << COUNT_BITS;
//3: 00000000000000000000000000000011
private static final int TERMINATED = 3 << COUNT_BITS;
除了CAPACITY
,其他几个变量代表了线程池的状态:
CAPACITY
:最大线程数RUNNING
: 高3位111(运行状态)SHUTDOWN
: 高3位000(此状态不再接收新的任务,但是会继续处理队列中的任务)STOP
: 高3位001(此状态不再接收新的任务,也不处理队列中的任务)TIDYING
: 高3位010(尝试中止线程池的一个状态变更,可以看作准备中止状态,在线程总数量为0的时候,会进入此状态,同时提供了一个钩子方法terminated())TERMINATED
:高3位011(钩子方法terminated()之后进入该状态,彻底中止了)
ctl:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static int ctlOf(int rs, int wc) { return rs | wc; }
可以看到ctl其实就是RUNNING
,通常ctl
与下面几个计算方法绑定的(下面的c就是ctl)
//~CAPACITY: 11100000000000000000000000000001
//return c & ~CAPACITY:获取c的高3位
private static int runStateOf(int c) { return c & ~CAPACITY; }
//CAPACITY:00011111111111111111111111111111
//c & CAPACITY:返回的就是c的低29位
private static int workerCountOf(int c) { return c & CAPACITY; }
//SHUTDOWN是0,小于SHUTDOWN,也就是判断是否是RUNNING
private static boolean isRunning(int c) { return c < SHUTDOWN;}
获取高3位
和低29
位,用来判断线程池的状态和线程总数量。
高3位:
判断线程池的状态低29位:
判断线程总数量
看完参数,开始看方法,主要方法就一个:
execute(Runnable command):
public void execute(Runnable command) {
if (command == null)
//不能为null
throw new NullPointerException();
int c = ctl.get();
//返回ctl的低29位,判断是否小于核心线程数
if (workerCountOf(c) < corePoolSize) {
//创建核心线程执行任务
if (addWorker(command, true))
//成功就返回
return;
//失败说明判断的时候小于,添加任务的时候不满足条件了,就重新获取ctl
c = ctl.get();
}
//走到这,说明大于核心线程数,则判断是否是运行状态,是就往队列中加任务
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
//如果不是RUNNING,就移除当前任务
if (! isRunning(recheck) && remove(command))
//失败就执行拒绝策略
reject(command);
//判断线程总数量是否为0(有可能线程执行任务报错了,然后就被销毁了)
else if (workerCountOf(recheck) == 0)
//创建非核心线程执行任务(注意这里是null)
addWorker(null, false);
}
//走到这,说明不是运行状态或者队列满了,就创建非核心线程执行任务
else if (!addWorker(command, false))
//失败就执行拒绝策略
reject(command);
}
总的来说,比较简单,大概流程是这样的:
详细方法列表
addWorker(分为2部分):
1.校验线程池状态和线程总数量:
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
//获取高3位
int rs = runStateOf(c);
//判断状态:
1.如果>= SHUTDOWN就继续判断
2.判断== SHUTDOWN,不是就返回false,是就继续判断
3.== SHUTDOWN,判断firstTask(添加的任务)是否为null,不是就返回false,是就继续
4.== SHUTDOWN,且添加的任务是null,判断workQueue是否不为null,是就返回false,不是就继续
//上面的几个判断,总结下来:
1.rs不是RUNNING,不是SHUTDOWN,就返回false
2.rs是SHUTDOWN,再判断firstTask == null,表示任务已经加到任务队列了,
3.最后再判断任务队列是否不为null,不同时满足就返回false,否则就继续往下走
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
//判断线程总数量
int wc = workerCountOf(c);
//core表示是创建核心线程还是非核心线程
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//CAS自增线程总数量,失败就回到retry,重新执行for(;;)
//retry是一个标记,表示跳出retry:标记的循环,正常的break是只跳出当前循环,用retry:标记之后,就可以跳出多个循环了
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get();
//判断状态是否改变了
if (runStateOf(c) != rs)
//相当于continue一次retry:标记的循环,正常的continue是只跳过当前循环的这一次,用retry:标记之后,就可以跳过标记的循环
continue retry;
}
}
这里主要是校验了线程池状态:
SHUTDOWN(不再接收新的任务,但是会继续处理队列中的任务)
STOP或者大于SHUTDOWN(不再接收新的任务,也不再处理队列中的任务)
CAS自增线程总数量
2.执行任务:
//走过上面的循环,说明线程池状态和总数量没问题
boolean workerStarted = false;
boolean workerAdded = false;
ThreadPoolExecutor.Worker w = null;
try {
//新建一个任务
w = new ThreadPoolExecutor.Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
//加锁,因为下面的workers是HashSet,线程不安全
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();
//largestPoolSize等于任务数量
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
//解锁
mainLock.unlock();
}
if (workerAdded) {
//开始执行任务的内容
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
//添加进失败任务
addWorkerFailed(w);
}
return workerStarted;
}
这里很简单,除了一些校验,就是把任务封装成一个Worker,丢到HashSet里面。
我们继续去看真正的执行任务 t.start()
:
可以看到,真正执行的是runWorker:
runWorker():
final void runWorker(ThreadPoolExecutor.Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock();
boolean completedAbruptly = true;
try {
//核心线程和非核心线程传进来的任务都不是null,所以会先执行task
//执行完了,就队列里面取任务
while (task != null || (task = getTask()) != null) {
w.lock();
//判断线程池状态是否 >= STOP,或者外部调的当前线程的interrupted()中断
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 {
//将队列中的任务数量自减
//注意,走到这,2个条件,task == null,
//getTask() == null,这个情况就是线程池状态是 >= STOP,或者是SHUTDOWN,队列里面没任务
processWorkerExit(w, completedAbruptly);
}
}
这里就是先执行自身的任务,执行完了就去队列里面取任务。
不过有个小知识,beforeExecute
和afterExecute
,是可以做一些小动作的,大家随意。
getTask():
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
//线程池状态
int rs = runStateOf(c);
//状态是SHUTDOWN,队列里面没任务,就返回null
//状态>= STOP,就把队列任务数量CAS -1,返回null
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
//获取线程总数量
int wc = workerCountOf(c);
//判断allowCoreThreadTimeOut,默认false,不回收
//判断是否为核心线程
//此处是有希望怼面试官的一点,有心的朋友记一下
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
//如果大于最大线程数量,或者allowCoreThreadTimeOut为true,并且非核心线程从队列中取任务,取的是null
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
//CAS,自减线程总数量
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
//核心线程不回收的原因,注意timed是可以控制回收的
//核心线程是take(),线程状态是WAITING ,无限制的等待,非核心线程是有参的poll(),线程状态是TIMED_WAITING,有时间限制的等待
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
//到这一步,说明是非核心线程从队列中取任务,队列中无任务
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
这里的重点其实就是一句话:
Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();
核心线程不回收,非核心线程回收的原因就在这。
这里也有个小知识,allowCoreThreadTimeOut
是可以控制核心线程也能回收的。
如果有朋友对take()
和poll()
不熟悉的朋友可以去翻博主的LinkedBlockingQueue的文章。
到这里,ThreadPoolExecutor的重点基本上就说完了,总结一下2个小知识:
beforeExecute
和afterExecute
,可以做一些小动作allowCoreThreadTimeOut
可以控制核心线程回收
下面再稍微看一下不太重要的2个地方:
不太重要的2个地方:
1.tryTerminate():
final void tryTerminate() {
for (;;) {
int c = ctl.get();
//是RUNNING,或者是TERMINATED,或者(是SHUTDOWN,并且队列不为空)
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
//返回不终止
return;
//线程总数量不是0
if (workerCountOf(c) != 0) {
//终止第一个线程
interruptIdleWorkers(ONLY_ONE);
return;
}
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//把状态改完TIDYING
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
//钩子方法,需要自己实现
terminated();
} finally {
//把状态改完TERMINATED
ctl.set(ctlOf(TERMINATED, 0));
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
}
}
是一个尝试中止线程池的方法,一般调用线程池的shutdown()
会用到,其主要作用其实就是那个钩子方法terminated()
,中止之前可以让你做点小动作。
2.retry:
不知道大家注意到这里面有一个写法retry:
,一个跳出多重循环的小技巧,下面给大家演示一下:
可以看到,此时是只跳出了内部的循环,最外层的循环还是会执行
可以看到,此时跳出了最外层的循环,用retry:就是可以跳出多个循环
可以看到,此时只跳出了当前循环的这一次
可以看到,此时跳出了标记的这次循环的这一次
总结下来就是:
break
和contine
可以直接到跳到retry:`标记的for循环
好了,重要的不重要的都看完了,看完这篇,还怼不了面试官,就从了他吧。
下篇文章写ThreadLocal
,或者开始写RocketMQ
专栏(进军MQ,冲啊)