前言(一定要看这里!!)
相信大家一定知道线程池执行的过程, 但如何配置线程大小, 如何真正的做到会用,如何通过量化的指标做到配置合理呢? 本文将从源码的角度分析线程池的实现,以及给出线程池如何量化使用的方式。
猛一看,是不是被篇幅吓到了,别害怕,我写的时候都不怕都能坚持,你看肯定也可以的,所有源码,基本上每一行都加了注释,除非特别简单的, 看不下去也收藏一下, 相信你以后一定用得上.
本文不仅仅用于分享,还希望能够在你听完分享以后,能够作为字典支持你随时查阅学习。
阅读本文之前要知晓,中断、UnSafe、BlockingQueue通知机制、AQS(状态、队列、CAS)。
任务提交
线程池提交过程回顾
左侧为: 类的继承关系.
右侧为: 类方法级别, 方法提交执行流程.
线程池状态转换
Executor
就是一个doug lea写的一个专门提交任务的接口
execute:提交任务。
ExecutorService
Executor接口声明的功能也太少了,因此接口ExecutorService接口继承了Executor,提供了更多的方法,需要ThreadPoolExecutor实现,这里主要简单介绍下各个方法的作用。
shutdown : 关闭线程池,不再接受新的任务,已提交执行的任务继续执行。
shutdownNow : 关闭线程池,不再接受新的任务,正在执行的任务尝试终止。
isShutdown : 确认线程池是否关闭。
isTerminated : 确认执行shutdown或者shutdownNow后,线程池是否关闭。
awaitTermination : 等待一段时间,如果到超时时间了,还没有terminated则返回false,反之则线程池已经terminated,返回true。
submit : 提交任务。
invokeAll : 执行所有任务,返回值为List<Future<T>> 。
invokeAny :执行N个任务,有一个执行完成就返回,返回值为 T(具体结果)。
AbstractExecutorService
AbstractExecutorService
实现了 ExecutorService
,提供了一些线程池通用的方法的实现。
invokeAll and invokeAny
两个方法使用的比较少,源码比较简单。invokeAny 主要是通过队列来获取最先执行完成的结果。
如果涉及到异步计算任务,建议使用 CompletableFuture。
submit
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
newTaskFor 实际上是构造一个 FutureTask 实例,FutureTask 实现了 RunnableFuture,RunnableFuture 继承了Runnable 和 Future 。
ThreadPoolExecutor
真正进入线程池源码学习阶段。
成员变量说明
// ctl存储线程池状态和线程数,integer共32位,那么用前三位表示线程池状态,后29位表示线程池数量。
// 初始化,状态为RUNNING,起始线程数是0。
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 这个就代表初始值 29位 (Integer.SIZE=32)
private static final int COUNT_BITS = Integer.SIZE - 3;
// 最大支持线程数 2^29-1
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// 以下为线程池的四个状态,用32位中的前三位表示
// 111 00000000000000000000000000000
private static final int RUNNING= -1 << COUNT_BITS;
// 000 00000000000000000000000000000 拒绝新的任务提交,会将队列中的任务执行完事,正在执行的任务继续执行.
private static final int SHUTDOWN =0 << COUNT_BITS;
// 001 00000000000000000000000000000 拒绝新的任务提交,清空在队列中的任务
private static final int STOP =1 << COUNT_BITS;
// 010 00000000000000000000000000000 所有任务都销毁了,workCount=0的时候,线程池的装填在转换为TIDYING是,会执行钩子方法terminated()
private static final int TIDYING=2 << COUNT_BITS;
// 011 00000000000000000000000000000 terminated() 方法执行完成后,线程池的状态会转为TERMINATED.
private static final int TERMINATED =3 << COUNT_BITS;
// 获取当前线程池的状态(前3位)
private static int runStateOf(int c) { return c & ~CAPACITY; }
// 获取当前线程池中线程数(后29位)
private static int workerCountOf(int c){ return c & CAPACITY; }
// 更新状态和数量
private static int ctlOf(int rs, int wc) { return rs | wc; }
// 小于判断C是不是小于S,比如runStateLessThan(var,STOP),那var就只有可能是(RUNNING,SHUTDOWN)
private static boolean runStateLessThan(int c, int s) {
return c < s;
}
// 是不是C >= S
private static boolean runStateAtLeast(int c, int s) {
return c >= s;
}
// 判断状态是不是RUNNING
private static boolean isRunning(int c) {
return c < SHUTDOWN;
}
// execute()方法提交的Runnable任务,如果当前没有获取到线程去执行任务,那么任务将放到这个阻塞队列中.
private final BlockingQueue<Runnable> workQueue;
// 这个锁用来保护下面的workers,访问workers必须获取这个锁.
private final ReentrantLock mainLock = new ReentrantLock();
// 设置包含池中的所有工作线程, 只有在持有主锁时才能访问.
- 这里为什么不使用线程安全的数据结构的原因主要两个:
1. 有复合操作, 增加 worker 的同时还要更新 largestPoolSize.
2. 中断线程时,如果不加锁,就可能出现并发的中断线程,引起中断风暴.
private final HashSet<Worker> workers = new HashSet<Worker>();
// 线程通信手段, 用于支持awaitTermination方法, awaitTermination的作用等待所有任务完成,并支持设置超时时间,返回值代表是不是超时.
private final Condition termination = mainLock.newCondition();
// 记录workers历史以来的最大值,每次获取之前都要获取主锁mainlock
// 每次增加worker的时候,都会判断当前workers.size()是否大于largestPoolSize,如果大于,则将当前最大值赋予largestPoolSize.
private int largestPoolSize;
// 计数所有已完成任务,每次获取之前都要获取主锁mainlock
// 每个worker都有一个自己的成员变量 completedTasks 来记录当前 worker 执行的任务次数, 当前线worker工作线程终止的时候, 才会将worker中的completedTasks的数量加入到 completedTaskCount 指标中.
private long completedTaskCount;
// 线程工厂,用于构造线程的时候加一些业务标识什么的.
private volatile ThreadFactory threadFactory;
// 拒绝策略,默认四种AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy,建议自己实现,增加监控指标.
private volatile RejectedExecutionHandler handler;
// 当线程池内线程数超过corePoolSize之后,空闲的线程多久之后进行销毁.
private volatile long keepAliveTime;
// 核心线程池空闲允许销毁的时间.
private volatile boolean allowCoreThreadTimeOut;
// 线程池核心线程池大小
private volatile int corePoolSize;
// 线程池可建立的最大线程数
private volatile int maximumPoolSize;
// 默认拒绝策略 AbortPolicy
private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();
// 安全控制访问(主要用于shutdown和 shutdownNow方法)
private static final RuntimePermission shutdownPerm = new RuntimePermission("modifyThread");
// threadPoolExecutor初始化的时候,还会初始化AccessControlContext对象的acc的值。
// 在threadPoolExecutor初始化的时候赋值,acc对象是指当前调用上下文的快照,其中包括当前线程继承的AccessControlContext和任何有限的特权范围,使得可以在稍后的某个时间点(可能在另一个线程中)检查此上下文。
private final AccessControlContext acc;
我们要清楚任务是Runnable(或者叫task或者command) , 线程是worker .
提交任务主流程分析
AbstractExecutorService.submit()
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
// 构造一个FutureTask实例,只要作用是获得返回值.这部分先不管,先看提交过程
RunnableFuture<T> ftask = newTaskFor(task);
//执行任务
execute(ftask);
return ftask;
}
ThreadPoolExecutor.execute()
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get(); // 获取当前线程池状态及线程数容器
if (workerCountOf(c) < corePoolSize) {// 判断当前线工作的线程数是否小于配置的核心线程数
if (addWorker(command, true))// 如果小于核心线程数就增加worker
return; // 增加worker成功且worker跑起来了就返回
c = ctl.get();// 线程池拒绝了增加worker, 重新获取线程池状态
}
// worker增加失败,或者当前线程池中线程数超过核心线程数.
if (isRunning(c) && workQueue.offer(command)) {// 判断当前线程池的状态是不是RUNNING,如果是则将任务加入到阻塞队列, offer是不阻塞的.
int recheck = ctl.get();// 获取当前线程池状态
if (! isRunning(recheck) && remove(command))// 判断当前线程池状态是不是RUNNING状态,不是就从workQueue中删除command任务
reject(command);//执行拒绝策略
else if (workerCountOf(recheck) == 0)//查看当前工作线程的数量
addWorker(null, false);//如果当前线程数是0,那么刚刚的任务肯定在阻塞队列里面了,这个时候开启一个没有任务的线程去跑.
}
// 当线程池当前的状态不是RUNNING,或者往workQueue添加worker失败
else if (!addWorker(command, false))//这个时候,队列满了,或者状态不是RUNNING,再次尝试开启一个worker
reject(command); //增加worker失败,执行拒绝策略.
}
ThreadPoolExecutor.addWorker(Runnable firstTask, boolean core)
//创建新的线程执行当前任务
//firstTask: 指定新增线程执行的第一个任务或者不执行任务
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();// 获取当前线程池状态及线程数的容器
int rs = runStateOf(c);// 获取当前运行状态
// Check if queue empty only if necessary.
// 如果线程池状态是SHUTDOWN、STOP、TIDYING、TERMINATED就不允许提交。
// && 后面的特殊情况,线程池的状态是SHUTDOWN并且要要执行的任务为Null并且队列不是空,这种情况下是允许增加一个线程来帮助队列中的任务跑完的,因为shutdown状态下,允许执行完成阻塞队里中的任务
if (rs >= SHUTDOWN &&! (rs == SHUTDOWN && firstTask == null &&! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);// 当前线程数
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))// 是否超过当前约定的最大值,超过就拒绝加入,直接返回,这里你要看core传的是什么,也就是当前处于哪一种情形,是核心线程池没用完还是咋的。
return false;
if (compareAndIncrementWorkerCount(c))//没超过约定值,那么通过CAS的方式增加worker的数量,增加成功就跳出外层循环。
break retry;
c = ctl.get(); // 再次获取当前线程池状态+线程数容器
if (runStateOf(c) != rs)// 判断当前运行状态是不是改变了
continue retry; //外层循环重新执行
// runStateOf(c) != rs 这个判断操作主要是看当前线程池的状态变没变,
// - 变了,就从外层循环重新执行,重新进行状态的检查。
// - 没变,从当前循环重新执行,重新执行CAS操作。
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);//构建worker,并将当前任务赋值给当前worker,这个地方要看一下new Worker的源码,如下,你会发现会直接new一个线程给当前worker对象
//Worker(Runnable firstTask) {
// setState(-1); // runWorker运行之前不允许中断
// this.firstTask = firstTask;
// this.thread = getThreadFactory().newThread(this); 一定要关注这个this
// }
final Thread t = w.thread; //获取当前worker的线程
if (t != null) {
final ReentrantLock mainLock = this.mainLock;//获取主锁
mainLock.lock();//加锁
try {
int rs = runStateOf(ctl.get());//获取当前运行状态
if (rs < SHUTDOWN ||//如果当前状态是<SHUTDOWN也就是RUNNABLE状态
(rs == SHUTDOWN && firstTask == null)) {//或者状态是SHUTDOWN并且当前任务是空的,这种就是单纯想新搞一个worker(thread),比如前面说的场景,阻塞队里里面还有,但当前已经是不允许提交的状态了。
if (t.isAlive()) // 检查当前线程是不是已经开始跑了
throw new IllegalThreadStateException();//刚刚new的线程怎么就抛弃了了,这种就抛异常,错误的线程状态异常。
workers.add(w);// 增加wokrer,这也就是为啥上面加锁的缘故,至于为啥要用hashSet,可以看上文成员变量wokers的说明。
int s = workers.size();//得到当前worker的总数
if (s > largestPoolSize)//当前worker的总数如果大于之前记录的线程池大小
largestPoolSize = s;//线程池大小重新赋值
workerAdded = true;//表示这次worker增加成功了
}
} finally {
mainLock.unlock();// 释放主锁
}
if (workerAdded) {// 如果worker增加成功了
t.start();//线程就跑起来
workerStarted = true;//表示线程已经跑起来了
}
}
} finally {
if (! workerStarted)//判断worker线程是否正常跑起来了
addWorkerFailed(w);//没成功跑起来,说明增加worker失败了
}
return workerStarted;//返回的是当前worker是否跑起来。
}
调用 t.start()
后,将执行 Worker 中的 run 方法,为啥呢,看Worker对象构造这块, 3)
这一步将 Worker
直接塞了进去,这么看就明白了吧。
Worker(Runnable firstTask) {
setState(-1); // runWorker运行之前不允许中断
this.firstTask = firstTask;
===> 3) this.thread = getThreadFactory().newThread(this);// 一定要关注这个this
}
Worker
好好看看Worker类,继承了AQS,实现了 Runnable。
继承AQS的原因: 线程只有两个状态,一个是独占表示线程正在运行,一个是不加锁空闲状态,用AQS的state区分,这里为啥不用ReentrantLock,主要原因是ReentrantLock是允许重入的。
private final class Worker extends AbstractQueuedSynchronizer implements Runnable
Woker.run()
/** Delegates main run loop to outer runWorker */
public void run() {
runWorker(this);
}
runWorker(this)
这里要说明一下, runWorker方法属于只要开启了一个线程,就一直循环执行getTask, getTask方法是可能阻塞的,当task==null&&getTask==null 这种就才会关闭当前任务.
final void runWorker(Worker w) {
Thread wt = Thread.currentThread(); //获取当前线程
Runnable task = w.firstTask; //获取当前线程任务
w.firstTask = null; //拿到任务后赋值给task,然后firstTask置为null。
w.unlock(); //设置允许中断,与Worker构造那里的setState(-1)遥相呼应
boolean completedAbruptly = true;//标识任务是不是立刻就完成了。
try {
while (task != null || (task = getTask()) != null) {//获取任务这个getTask单独讲
w.lock();//尝试加锁
//如果(线程池的状态>=STOP或者(线程已中断并且线程状态>=STOP))并且当前线程没有被中断。
// 两种情况:
//1)如果当前线程池的状态是>=Stop的,并且当前线程没有被中断,那么就要执行中断。
//2)或者当前线程目前是已中断的状态并且线程池的状态也是>=Stop的(注意Thread.interrupted是会擦除中断标识符的),那么因为中断标识符已经被擦除了,那么!wt.isInterrupted()一定返回true,这个时候还是要将当前线程中断。第二次执行runStateAtLeast(ctl.get(), STOP)相当于一个二次检查。
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();//执行任务,这个实际上调用的是传入的ftask的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;//最后将task置为null
w.completedTasks++;//已完成的任务计数器+1
w.unlock();//释放当前线程的独占锁
}
}
completedAbruptly = false;//任务执行完事,经过上面一堆过程,标识着任务不是立刻完成.
} finally {
// 一定要注意。执行到这里说明task == null并且getTask()返回null。说明当前线程池中不需要那么多线程来执行任务了,可以把多于corePoolSize数量的工作线程干掉,也可能被中断被SHUTDOWN等,也会导致getTask返回Null
processWorkerExit(w, completedAbruptly);//任务退出过程,单独分析
}
}
getTask
获取任务的方法
要注意的是调用getTask方法的地方是一个while死循环, 只要getTask有返回值,那么就不会退出循环.
退出循环就说明改销毁超过核心线程数的那部分线程了.
private Runnable getTask() {
boolean timedOut = false; //最后获取任务是不是超时了
for (;;) {// 死循环
int c = ctl.get();// 获取容器
int rs = runStateOf(c);//获取当前的运行状态
// 如果当前状态是>=SHOTDOWN状态&&(运行状态是STOP或者队列是空的).
// 1)如果线程池的状态是>=STOP状态,这个时候不再处理队列中的任务,并且减少worker记录数量,返回的任务为null,这个时候在runRWorker方法中会执行processWorkerExit进行worker的退出操作.
// 2)如果线程池的状态是>=SHUTDOWN并且workQueue为空,就说明处于SHOTdown以上的状态下,且没有任务在等待,那么也属于获取不到任务,getTask返回null.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();//减少线程池中线程的数量计数,在runWorker中finally中进行线程退出
return null;//返回null
}
int wc = workerCountOf(c);//获取当前wokrer的数量
// 是否开启超时机制.
// 如果核心线程数允许超时,则timed为true,开启超时机制.
// 如果核心线程数不允许超时,那么就看当前总线程数是不是>核心线程数,如果大于,则timed为true,当前woker开启超时机制.
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
// (如果当前线程池大小超过最大的设定值 或者 线程设置允许超时且当前woker获取任务超时) 并且当前线程池大小不是零或阻塞队列是空的,这种就返回null,并减少线程池线程计数.
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
// 满足上述条件就减少worker计数.这里为啥不用decrementWorkerCount()呢,
// 上面使用decrementWorkerCount()是因为确定不管是什么情况下,数量都要减,多减一次也没事,因为这个时候就是要关闭线程池释放资源.
if (compareAndDecrementWorkerCount(c))//这里不一样,线程池的状态可能是RUNNING状态,多减一次,可能导致任务获取不到worker去跑.
return null;
continue;//false 跳出本次循环重新检查.
}
try {
//判断是否允许超时,允许超时用poll设置超时时间,不允许就使用take依赖超时机制
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;//任务不等于null 就返回
timedOut = true;//这种一定是超时导致的,所以timedOut设置为true
} catch (InterruptedException retry) {//因为阻塞队列poll take都是使用快速响应中断的加锁方式(lockInterruptibly()),因此需要捕获中断异常.并且这方法是用的Thread.interrupted()判断的,有个特点会擦除中断状态,这就说明getTask方法是不响应中断的.
timedOut = false;
}
}
}
processWorkerExit
任务异常退出, 则在加个worker回来, 当前任务是丢了的.
任务不是异常退出:
1) 如何核心线程允许超时,当任务队列中还有任务,那么就必须保证线程池中有一个worker,没有就在这个方法里面执行addWorker.
2) 如果核心线程不允许超时,就得保证当前线程池中线程数量>=核心线程数,如果当前线程池中线程数量<核心线程数,依然要增加一个worker,执行addWorker.
private void processWorkerExit(Worker w, boolean completedAbruptly) {
//任务是不是突然完成啦,完成就将工作线程数量-1
//如果completedAbruptly为true,则说明线程执行时出现异常,需要将workerCount数量减一
//如果completedAbruptly为false,说明在getTask方法中已经对workerCount进行减一,这里不用再减
if (completedAbruptly)
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//更新已完成任务的数量的统计项
completedTaskCount += w.completedTasks;
//从worker中移除任务
workers.remove(w);
} finally {
mainLock.unlock();
}
//尝试关闭线程池,但如果是正常运行状态,就不会关闭,这个是否关闭的临界条件在分析tryTerminate单独会说.
tryTerminate();
int c = ctl.get();
//这个地方比较绕,要好好看哈.
// completedAbruptly=true代表异常结束的,具体为啥可以看runWorker中的代码,没有异常的话会走completedAbruptly=false的.
//前提当前线程池的状态是SHUGTDOWN或者RUNNING,如果不是这两个状态,说明线程已经停止了,啥都不会要干了.
//如果任务是异常结束的,就增加worker
//注: 别问我为啥上面要删除worker,还要再加,不删是不是不用加了. 明确下那个任务已经退出getTask那块的死循环了,永远回不去了,只能新增worker.
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);
}
}
tryTerminate
interruptIdleWorkers(ONLY_ONE); 是否好奇为啥这里只中断一个worker呢, 这里就涉及到了线程池的优雅退出了.
当执行到 ===>(1
的时候, 线程池只能处于两种状态:
1) STOP
状态 , 这个时候 workQueue 可能是有值的 , workQueue 在清空的过程中了.
2) SHUTDOWN
状态并且 workQueue 是空的 .
这两种状态都是说明, 线程池即将关闭, 或者说空闲的线程此时已经没用了,这个时候随手关一个, 反正要关,早关晚关而已.
//根据线程池状态判断是否结束线程池
final void tryTerminate() {
for (; ; ) {
int c = ctl.get();
//RUNNING状态,不能终止线程池
//线程池状态是TIDYING或TERMINATED说明线程池已经处于正在终止的路上,不用再终止了.
//状态为SHUTDOWN,但是任务队列不为空,也不能终止线程池
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && !workQueue.isEmpty()))
return;
//工作线程数量不等于0,中断一个空闲的工作线程并返回
===>(1 //这个时候线程池一定是STOP的状态或者SHUTDOW并队列不为空,这两种情况要么就是尝试种植因此这个时候尝试中断一个空闲worker
if (workerCountOf(c) != 0) {
interruptIdleWorkers(ONLY_ONE);
return;
}
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 设置线程池状态为TIDYING,如果设置成功,则调用terminated方法
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
//子类实现
terminated();
} finally {
// 设置状态为TERMINATED
ctl.set(ctlOf(TERMINATED, 0));
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
}
}
interruptIdleWorker
//中断空闲线程,因为空闲线程一定都是通过LockSupport.park使其处于等待状态,此时如果执行中断interrupt命令,当前空闲线程会即刻抛出中断异常,并被唤醒.
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//遍历worker,根据onlyOne判断,如果为ture只中断一个空闲线程
for (Worker w : workers) {
Thread t = w.thread;
//线程没有被中断并且线程是空闲状态tryLock()判断是否空闲
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
指标获取
实时指标获取
threadPoolExecutor.getActiveCount(): 返回正在执行任务的大致
线程数 , 注意: 不是精确值, 所以目前看作用就只有展示用, 不能作为临界条件使用.
public int getActiveCount() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();//阻止新的worker加入并执行
try {
int n = 0;
for (Worker w : workers)//当前循环的过程,worker的状态也是不断状态的比如会出现刚刚统计完,但没统计结束,他就跑完了,这种就统计错了.
if (w.isLocked())
++n;
return n;
} finally {
mainLock.unlock();
}
}
threadPoolExecutor.getCompletedTaskCount() : 返回已执行完成的任务的大致
总数,为啥不精确,原因与getActiveCount
相似, 就不分析了.
threadPoolExecutor.getTaskCount(): 返回计划执行的任务的大致
总数, 为啥不精确, 原因与getActiveCount
相似, 就不分析了.
threadPoolExecutor.getLargestPoolSize(): 返回在池中历史创建的最大线程数.
threadPoolExecutor.getPoolSize(): 返回池中当前的线程数.
固定指标获取
线程池构造的时候指定的
threadPoolExecutor.getRejectedExecutionHandler(): 返回不可执行任务的当前处理程序
threadPoolExecutor.getThreadFactory(): 返回用于创建新线程的线程工厂
threadPoolExecutor.getCorePoolSize(): 返回核心线程数
threadPoolExecutor.getKeepAliveTime(): 返回线程保持活动时间
threadPoolExecutor.getMaximumPoolSize(): 返回允许的最大线程数
threadPoolExecutor.getQueue(): 返回此执行程序使用的任务队列
拒绝策略
AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy
拒绝策略建议自己实现
拒绝策略建议自己实现,如下, 加上实时统计指标, 方便对线程池容量, 以及使用是否合理进行分析, 这一点真的在实战中特别重要.
public class MonitoredCallerRunsPolicy implements RejectedExecutionHandler {
private final static Logger LOG = LoggerFactory.getLogger(MonitoredCallerRunsPolicy.class);
private final String threadPoolName;
public final static String THREAD_CALLER_RUN = "ThreadPoolCallerRun";
public MonitoredCallerRunsPolicy(String threadPoolName) {
this.threadPoolName = threadPoolName;
}
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor threadPoolExecutor) {
if (!threadPoolExecutor.isShutdown()) {
r.run();
// todo 增加必要实时监控指标
LOG.info("thread pool:{} trigger caller Run", threadPoolName);
Cat.logEvent(THREAD_CALLER_RUN, threadPoolName);
}
}
}
其他方法说明
shutdown
关闭线程池,不再接受新的任务,已提交执行的任务继续执行。
注意: 正在运行的任务, 运行完事并且当前workQueue已经是空的了, 就直接退出了.线程走向终结.具体源码为getTask的
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;//返回null就要执行processWorkerExit退出的逻辑了.
}
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();//安全策略判断
advanceRunState(SHUTDOWN);//RUNNING->SHUTDOWN状态转换
interruptIdleWorkers();//中断所有空闲线程,这个方法上面分析了
onShutdown(); // ScheduledThreadPoolExecutor预留的钩子
} finally {
mainLock.unlock();
}
tryTerminate();//尝试关闭空闲线程.这个方法上面也说过了
}
shutdownNow
关闭线程池,不再接受新的任务,正在执行的任务尝试终止。
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();//权限验证
advanceRunState(STOP);//线程池的状态置为STOP
interruptWorkers();//强制中断所有状态不是-1的,就是所有启动了的workers.
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers)//循环所有的worker
w.interruptIfStarted();//直接执行中断
} finally {
mainLock.unlock();
}
}
void interruptIfStarted() {
Thread t;
//只有刚刚构建的worker的时候,状态state值是-1(这里也能体现刚构建的worker无法被中断),其他情况都是>=0的
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
isShutdown
确认线程池是否关闭。判断状态是不是RUNNING.
public boolean isShutdown() {
return ! isRunning(ctl.get());
}
isTerminated
确认执行shutdown或者shutdownNow后,线程池是否关闭。
源码很简单就是看当前线程池的状态是不是TERMINATED
awaitTermination
等待一段时间,如果到超时时间了,还没有terminated则返回false,反之则线程池已经terminated,返回true。
public boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (;;) {
if (runStateAtLeast(ctl.get(), TERMINATED))//如果状态已经是TERMINATED
return true;
if (nanos <= 0)
return false;
nanos = termination.awaitNanos(nanos);//通知机制tryTerminate中将线程池状态设置为TERMINATED后,发通知到这里
}
} finally {
mainLock.unlock();
}
}
isTerminating
是不是正在停止中,看下面源码分析吧,一下子就明白了
public boolean isTerminating() {
int c = ctl.get();
return ! isRunning(c) && runStateLessThan(c, TERMINATED);//看看状态是不是TIDYING/STOP/SHUTDOWN如果是这三种状态就返回true,此时线程池正在停止,不是这三种状态并且不是RUNNING状态,那么就是TERMINATED状态,返回false,此时线程池已经终止了.
}
private static boolean runStateLessThan(int c, int s) {
return c < s;
}
prestartCoreThread
启动一个空闲的线程作为核心线程,
- 如果核心线程数已到阈值, 会加入失败, 返回false, 如果线程池处于SHUTDOWN以上的状态也返回false,
- 只有真正这个线程调用start方法跑起来, 才会返回true.
public boolean prestartCoreThread() {
return workerCountOf(ctl.get()) < corePoolSize &&
addWorker(null, true);
}
prestartAllCoreThreads
启动所有核心线程,使他们等待获取任务。
public int prestartAllCoreThreads() {
int n = 0;
while (addWorker(null, true))//null代表空闲线程,true代表是增加的是核心线程
++n;//死循环增加空闲 worker 而已
return n;
}
FutureTask
介绍
FutureTask简单说就是一个任务,他可以被提交,也可以用来获取结果, 获取执行的状态, 也可以被取消.
成员变量
* NEW -> COMPLETING -> NORMAL
* NEW -> COMPLETING -> EXCEPTIONAL
* NEW -> ==> -> CANCELLED
* NEW -> INTERRUPTING -> INTERRUPTED
// 1个初始态:NEW
// 2个中间态:COMPLETING,INTERRUPTING
// 4个终止态:NORMAL,EXCEPTIONAL,INTERRUPTED,CANCELLED
private volatile int state;
private static final int NEW = 0;//任务的初始状态
private static final int COMPLETING = 1;//正在设置运行的结果
private static final int NORMAL = 2;//任务正常执行完成
private static final int EXCEPTIONAL = 3;//任务执行过程中发生异常
private static final int CANCELLED = 4;//任务被取消
private static final int INTERRUPTING = 5;//正在中断运行中的任务
private static final int INTERRUPTED = 6;//任务被中断
//要执行的任务
private Callable<V> callable;
//返回的结果或者异常
private Object outcome;
//当前执行任务的线程
private volatile Thread runner;
//Treiber栈,先进后出,线程安全的栈,存储等待任务执行完毕的线程信息
private volatile WaitNode waiters;
CAS
//this:当前对象实例
//偏移量:当前属性相对于this对象实例的对象头的偏移量
//当前预期状态:NEW
//将要更新成的值:COMPLETING
UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)
方法分析
构造方法
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;//任务赋值
this.state = NEW; //设置当前任务初始状态为new
}
获取成员变量相对于对象实例的偏移量
private static final sun.misc.Unsafe UNSAFE;
//获取偏移量的作用是为了支持UnSafe中原子的更改状值的操作,比如支持下面的大标题 <CAS操作>
private static final long stateOffset;//state属性偏移量
private static final long runnerOffset;//runner属性偏移量
private static final long waitersOffset;//waiter属性偏移量
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> k = FutureTask.class;
//getDeclaredField方法获得某个类的所有声明的字段,即包括public、private和proteced,但是不包括父类的申明字段
//UNSAFE.objectFieldOffset获取value字段在对象中的偏移量(其实就是一个字段到对象头部的偏移量,通过这个偏移量可以快速定位字段)这个对象实际上是指对象实例的,引用对象实例在堆中.
stateOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("state"));
runnerOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("runner"));
waitersOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("waiters"));
} catch (Exception e) {
throw new Error(e);
}
}
run方法
public void run() {
//判断当前任务的状态是不是初始状态并且当前之前没有线程执行过
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();//这里真正执行业务代码
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);//处理异常
}
if (ran)
set(result);//处理结果
}
} finally {
runner = null;
int s = state;
if (s >= INTERRUPTING)//如果是中断中的状态就让出cpu时间等待任务状态变为INTERRUTPTED,因为取消操作是两步,具体看cancel中的分析吧
handlePossibleCancellationInterrupt(s);
}
}
setException 方法
protected void setException(Throwable t) {
//降低当前任务的状态从new更新成completing,更新成功说明任务已经执行完成,进行结果复制
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = t;//设置返回结果
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // 将任务的状态设置为终态,这里不用putIntVolatile的原因是因为当前任务的state字段就是被volatile修饰了.
finishCompletion();//任务执行完成最后收尾
}
}
set 方法
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;//设置返回结果
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // 设置当前任务的最终状态为NORMAL
finishCompletion();//任务执行完成最后收尾
}
}
finishCompletion
资源释放 比较简单
private void finishCompletion() {
// assert state > COMPLETING;
for (WaitNode q; (q = waiters) != null;) {//死循环唤醒等待节点的线程,并回收资源
if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
for (;;) {
Thread t = q.thread;
if (t != null) {
q.thread = null;
LockSupport.unpark(t);
}
WaitNode next = q.next;
if (next == null)
break;
q.next = null; // unlink to help gc
q = next;
}
break;
}
}
done();
callable = null; // to reduce footprint
}
handlePossibleCancellationInterrupt
//这个方法就很简单了,就是说如果是中断中的状态,就让出CPU时间,此处为死循环,等待最终状态变为已中断的状态.
private void handlePossibleCancellationInterrupt(int s) {
if (s == INTERRUPTING)
while (state == INTERRUPTING)
Thread.yield(); //让出CPU时间等待任务完成
}
cancel
public boolean cancel(boolean mayInterruptIfRunning) {
if (!(state == NEW &&
UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))//设置任务状态为中断中或者取消
return false;//如果状态不是new或者设置状态失败,返回false说明取消失败
try { // in case call to interrupt throws exception
if (mayInterruptIfRunning) {
try {
Thread t = runner;
if (t != null)
t.interrupt();//如果是中断的方式,调用线程的中断方法.
} finally { // final state
UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);//并设置当前任务的最终状态为INTERRUPTED
}
}
} finally {
finishCompletion();//收尾工作,上文介绍过
}
return true;
}
整体执行流程
- 将当前线程设置为runner
- 判断任务的状态是NEW则执行任务
- 任务执行成功,outcome赋返回值,任务执行失败,outcome赋异常,给outcome赋值之前会先将状态设置为COMPLETING.
- outcome赋值完成后,设置最终状态NORMAL 或者 EXCEPTIONAL
- 唤醒waiters中所有的线程,并做资源的释放
- 检查是否有中断被遗漏, 如果有, 等待中断状态完成
个人观点输出
1)下面的线程池定义会有什么问题?
public class MXThreadPool {
private final static int CORE_SIZE = 80;//核心线程数
private final static int MAX_SIZE = 900;//最大线程数
private final static long ALIVE_TIME = 500;//超过核心线程数的其他线程存活时间
private final static int QUEUE_SIZE = 160;//队列大小
private static volatile ThreadPoolExecutor threadPoolExecutor;
public static ThreadPoolExecutor getInstance(){
if(threadPoolExecutor == null){
synchronized (CommonThreadPool.class){
if(threadPoolExecutor == null){
String poolName = "mx-common";
threadPoolExecutor = new ThreadPoolExecutor(
CORE_SIZE,
MAX_SIZE,
ALIVE_TIME,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(QUEUE_SIZE),
new CommonThreadFactory(poolName),
new MonitoredCallerRunsPolicy(poolName));
}
}
}
return threadPoolExecutor;
}
}
先看图(从右往左看):
图上描述的是当处于业务高峰的时候, 此时阻塞队列是满的, 线程总量也达到最大线程数的阈值,
- 这个时候所有的线程在执行完创建线程指定的firstTask后,都会阻塞在BlockingQueue的take或者pull方法上去,
- 此时依赖队列的通知机制了,队列中有一个每增加一个就通知一个线程, 这就有问题了, 当第一波高峰来了以后,所有线程都在等待任务, 但此时队列大小只有160, 因此,同一时刻, 只有160个线程可以被唤醒, 只有160个线程可以工作.
文字描述确实苍白无力, 可能是我文字功底太差吧, 哈哈
阶段总结
这里建议线程池的最大值设置与阻塞队列的大小保持一致, 这样所有线程都可以参与工作, 不会出现大量线程除以timed_waiting的情况.
那么具体刚创建一个线程池的时候需要怎么指定大小呢 ?
我的建议:
- 所有线程池都使用自定义的,别用Executors下面的, 这个懂得人用什么都是对的, 不懂得还是自定义好一点, 方便规避问题.
- 是根据业务场景 ,做好线程池资源的隔离, 将快满服务分别创建线程池进行管理.
- 项目刚创建的时候,可以先按照网上的说法根据业务区分CPU密集还是IO密集型服务,定义线程数,但重点是要自定义拒绝策略,加上必要的监控指标比如getActiveCount、getLargestPoolSize等(详见实时指标),上线后根据监控指标动态调整,这是一个调优的过程.
2) LinkedBlockingQueue 和 ArrayBlockingQueue对比
在自定义线程池的时候,你应该使用哪种队列呢, 上google或者baidu几乎所有的文章都推荐使用LinkedBlockingQueue
我这边不这么认为, 通过看源码, 发现在构建线程池这里并指定队列大小的情况下, 使用ArrayBlockingQueue的性能会更好, 因为不涉及到队列的扩容拷贝, 也不涉及到队列中元素移动, ArrayBlockingQueue操作完全更简单, 比维护一个链表, 性能更好.
3) Worker为什么不使用ReentrantLock来实现呢?
- ReentrantLock是可重入的,但我们这里线程资源的控制要求是不可重入的 !!!
4)在runWorker方法中,为什么要在执行任务的时候对每个工作线程都加锁呢?
- shutdown 方法与 getTask 方法存在竞态条件,其实很多地方加锁的原因都是竞态条件的原因,具体每个地方都要看源码。
- 另一种角度来看,更方便管理需要同步的场景。
5)为啥workers用hashSet+锁实现, 而不是使用线程安全的数据结构 ?
主要是因为这里有些复合的操作,比如说将worker添加到workers后,我们还需要判断是否需要更新largestPoolSize等,workers只在获取到mainLock的情况下才会进行读写,另外这里的mainLock也用于在中断线程的时候串行执行,否则如果不加锁的话,可能会造成并发去中断线程,引起不必要的中断风暴(大量线程去中断,大量阻塞在synchronized,进行锁竞争CPU飚高)。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。