为什么要引入线程池?
线程的创建是和运行是耗性能的,而系统资源是有限的。引入线程池,是为了高效的利用系统资源。 线程池,广义上理解就是存放线程的一个池子,一个个具体的线程提交到线程池中即为一个具体的任务。 线程池来管理这些任务的运行。
线程池所做的事情
1、任务提交到线程池
2、任务提交到线程池中,是如何执行的,假设有固定的10个线程来执行。
3、如果任务突然多了,10个线程都在不停的执行任务,新接收的任务,那么把这个任务压着,等空闲时候再执行,则需要一个存放任务的队列,queue,假设是有界的队列,执行存100个。
4、计划的10个线程,和接收的任务不平衡,忙不过来,此时期望能有临时的线程来运行任务,从queue中取出任务来执行,或者新接收的任务交给临时线程来执行。假设计划有20个临时线程。
5、如果固定的10个线程都在忙碌,queue 中已经有了100个任务,20个临时线程也在忙碌,这时候新接收的任务(我们把固定的线程和临时的20个线程统一叫做工作线程),线程池处理不过来了,也就是工作线程满了,队列满了,针对该场景可能会直接丢弃任务,或者告知请求方,牛仔很忙等等处理方式,不妨把此操作定义为线程池的拒绝策略。
6、线程池状态,需要让外界知道,线程池目前处理什么样状态,通过该状态,判断是否可以把任务提交给线程池
7、线程池的状态管理,任务的管理,工作线程的管理
线程池设计原理
先空着
java 线程池体系
java线程池体系结构
ThreadPoolExecutor
线程池状态和工作线程数
ctl
runState 线程池运行状态,通过runStateOf()获取
wokerCount 线程池中的工作线程数(核心线程数+非核心线程数),通过workerCountOf()获取
线程池的5个状态
RUNNING , 运行状态,接收新的任务,并执行它
SHUTDOWN , 不再接收新的任务,但阻塞队列中的任务会继续执行
STOP ,不再接收新的任务,阻塞队列中的任务也不再继续执行
DIDYING ,
TERMINATED ,terminated() 执行完
上面提高的固定线程和临时线程,只要它们创建,都作为工作线程,那么肯定需要一个计数器来记录有多少个工作线程。
为此我们需要2个变量,一个来记录线程的状态位,一个用来记录工作线程的数量。
如果用二进制 0 1 来表示线程池的5个状态,至少需要3位(2位只能表示4种状态[00、01、10、11],3位可以表示8种状态)。
用一个变量来表示线程池状态和工作线程数 ctl,它是一个int类型,int 类型 在jvm中占4个字节,每个字节二进制8位表示,即32(1110 0000 0000 0000 000 0000 000 0000 )。
runState,线程池状态如果用二进制,3位即可表示全,所以,ctl的高3位(二进制左面的3位)表示线程池的状态。
wokerCount,工作线程数量 ,32位去掉已经被占用的3为,还有29位,把这29位叫做低29位,用来存储工作线程数量。所以工作线程数量是有上限的。
并提供获取ctl 高3位和低29位的方法 , 获取的结果即为 runState 和 wokerCount 。
线程池状态生命周期
// 高3位表示 runState ,低29位表示 wokerCount
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3; // 29
// 1 << 29 再减少1 ,二进制表示 0001 1111 1111 1111 1111 1111 1111 1111
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// ~ CAPACITY 二进制 1110 0000 0000 0000 0000 0000 0000 0000
// runState is stored in the high-order bits
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;
// Packing and unpacking ctl
private static int runStateOf(int c) {
return c & ~CAPACITY; // 获取的是ctl的高3位
}
private static int workerCountOf(int c) {
return c & CAPACITY; // 获取的是ctl的低29位
}
private static int ctlOf(int rs, int wc) { return rs | wc; }
private static boolean isRunning(int c) {
return c < SHUTDOWN; // 直接判断ctl , SHUTDOWN=0 , running 是负数,贼简单的判断方式
}
构造函数(核心参数)
corePoolSize,核心线程数,指定了线程池计划有多少个核心线程数
maximumPoolSize ,最大线程数,指定线程池最多有多少个临时线程数(非核心线程)
keepAliveTime,非核心线程销毁前的保活时间
unit 时间单位
workQueue , 存放任务的队列,是一个阻塞队列
threadFactory , 创建线程池中工作线程的工厂
handler , 拒绝策略,当线程池满了,执行的拒绝策略
源码如下
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
// 常规检查
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
拒绝策略
线程池拒绝策略,有4种
1、AbortPolicy ,ThreadPoolExecutor 的默认拒绝策略
2、CallerRunsPolicy ,
3、DiscardPolicy ,
4、DiscardOldestPolicy ,
5、拓展拒绝策略,用户可以拓展自己的拒绝策略,通过实现 RejectedExecutionHandler接口,重写rejectedExecution()
execute()
workQueue , 存任务队列,当工作线程数达到了核心线程数,会把任务存放到阻塞队列中
addWorker(Runnable firstTask,boolean core) ,后面会具体届时,此处把它理解成一个创建工作线程即可。
execute(Runnable command) 方法签名:
command ,即要执行的任务
在将来的某个特定时机,执行该任务。可能会创建进一个新的工作线程来执行,也可能复用线程池中已经存在的工作线程来执行。
如果该任务不能被成功提交,可能是因为线程池已经被shutdown 了, 也可能是因为当前线程池已经达到了上限(核心线程数、工作线程数 均达到了上限,任务队列满了)。
如果不能被成功提交,线程池的拒绝策略会生效
execute() 执行流程:
execute() 流程 整体上分为3步骤
1、如果当前工作线程数小于核心线程数,会创建一个核心线程来执行command
2、如果当前的工作线程数,大于核心线程数,把command提交给任务队列 workQueue。此处会执行一个双重检查,检查当前线程池的状态是否是运行中(running),如果不是running,把任务从 workQueue中移除,并执行拒绝策略,如果是running,并且工作线程数小于最大线程数,创建一个非核心线程,addWorker(null,false),这个非核心线程会从workQueue中取任务执行。
3、如果达到了核心线程数上限,任务队列 workQueue 满了,不能继续提交任务。尝试创建一个非核心线程来执行 addWorker(command,false),意思是创建一个非核心线程,并立即执行command。
如果非核心线程不能创建成功,则执行拒绝策略
execute()源码如下:
public void execute(Runnable command) {
// 省略了常规检查代码
int c = ctl.get(); // 取ctl 快照
if (workerCountOf(c) < corePoolSize) { // 当前的工作线程数量小于核心线程数,创建核心线程
if (addWorker(command, true)) // 因为是并发场景,其他任务抢先了,所以会有创建核心线程失败的场景,失败的场景可能是线程池被外界shutdown,或者线程池核心线程数满了
return; // 如果核心线程成功创建,则直接返回 ,否继续流程
c = ctl.get(); // 重新取ctl 快照,因为上面已经创建核心线程数失败了,c 不是最新的了
}
// 如果当前线程池状态已经不是运行中了,会直接走拒绝策略(外界关闭了线程池)
if (isRunning(c) && workQueue.offer(command)) { //当前线程池是running,则把任务添加到阻塞队列中,
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command); // 任务添加到阻塞队列中,要做二次检查,当前线程池如果被关闭要从阻塞队列中移除任务,并执行拒接策略,
else if (workerCountOf(recheck) == 0) // 如果当前的工作线程数小于最大线程数,新建非核心线程
addWorker(null, false); // 创建非核心线程 firstTask 是null core 是 fase
}
else if (!addWorker(command, false))
reject(command); // 达到了核心线程数,阻塞队列满了,也不能创建非核心线程了,执行拒接 和 上面的拒接2个是不一样场景触发的
}
addWorker()
addWorker()方法签名:
创建一个工作线程,可以是核心线程,也可以是非核心线程(core maxi queue 控制)。
通过给定的参数 (firstTask 、 core)和线程池状态,在线程池中创建工作线程。线程池状态控制能不能创建线程,给定的参数控制创建什么样的线程,核心线程或者非核心线程。
工作线程成功创建并成功启动之后,线程池中工作线程数量也会随之调整。
如果给了第一个任务,新创建的工作线程会执行第一个任务。
firstTask 是否是第一个任务(是不是Worker执行的第一个任务),如果是第一个任务,则不能是空,这个新建的工作线程会执行这个任务。创建核心线程 firstTask 肯定不是空的,非核心是空或者不是空。
core , 核心线程 和 非核心线程的标识, true 为核心线程,false 为非核心线程 addWorker(Runnable firstTask,boolean core)
预备知识:
doug lea 老早前是写C的,lea老爷写Java的习惯会带有写C的思想在里面,变量名称,判断语句处理等。
// 定义一个预置点bet
bet:
for(;;){
inner: // 定义一个预置点 inner
for(;;){
break inner; // 跳出内层循环 类似 C的goto 语法,Java 没有 goto
}
break bet; // 会跳出外层循环
}
System.out.println("the end");
Worker 类的定义
继承AQS , 并实现 Runnable 接口 。
final class Worker
extends AbstractQueuedSynchronizer
implements Runnable{
// 省略其他细节
final Thread thread; // Worker 是一个任务线程,thread 来执行 Worker
Runnable firstTask; //初始化的任务,addWorker 的 firstTask 参数,创建非核心线程,且不是第一个任务时候,firstTask 是 null
volatile long completedTask; //
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker AQS 的 state
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this); // 线程工厂创建的线程 入参是 this ,即当前的 Worker
}
public void run(){
runWorker(this); // 调度的是TreadPoolExecutor#runWorker()
}
// 重写 AQS 的 钩子函数
}
addWorker() 执行流程:
死循环for,套死循环for,看到这不要慌,这是doug lea 的写法, 拆开看,整体上分为2块内容 :
1、2个for循环,是检查,类似卫语句写法,先把不成立的摘出来 return ,如果条件成立CAS 操作 工作线程数量+1(如果后续操作失败,再回滚) 。
2、创建 Worker(工作线程) ,并把Worker 添加到 workers 中,成功添加到 workers 中的 Worker 会被启动
3、(finally 事情)如果Worker 被添加到 workes 中,则启动线程,如果没有则回滚,把此处和1、2操作分开,但是它们是有关系的,而且非常重要(工作线程的启动)。
如下是 workers 的定义, HashSet
final HashSet<Worker> workers = new HashSet<Worker>() // addWorker() 中 新建的线程会被添加到 workers 中,并如果添加到workers 中则会启动该线程,否则会从 workers 中回滚 Worker
addWorker() 源码
private boolean addWorker(Runnable firstTask, boolean core) {
retry: // 类似 goto 语句 用于跳出循环
// for(;;) 方式,退出循环肯定是有多种场景条件满足退出
for (;;) {
int c = ctl.get();
int rs = runStateOf(c); // runState 线程池运行状态
/*
1、RUNNING 状态会接收新任务,
2、SHUTDOWN 状态,不会接收新任务,但是会继续执行queue中的任务
3、如果 rs > SHUTDOWN , 直接结束 ; 否则对里面组合(&&关系)操作取反 组合中有一个是false 则 取反就是true
如果 rs = SHUTDOWN ,还要判断firstTask 是不是第一个任务,
*/
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN // 如果外部调用了(或者已经调用了) shutdown() 或者shutdownNow(),线程池已经SHUTDOWN 了,
&& // SHUWDOWN 状态是不在接收新的任务,但是queue中的任务会继续执行
! (
// 线程成状态是SHUTDOWN 并且不是第一个任务,并且queue 不为空(阻塞队列中有任务没有执行) ,需要消费调queue里面的任务,
rs == SHUTDOWN
&&
firstTask == null
&&
! workQueue.isEmpty()
)
)
return false;
for (;;) {
int wc = workerCountOf(c); // 取工作线程数量
if (
// || 关系,只要一个不成立则结束,不创建 Worker
wc >= CAPACITY // 如果工作线程数达到了池的上线(int类型的低29全是1),则不创建线程
||
// core = true , 和核心线程数比较,false 和 最大线程数(核心线程数+非核心线程数)比较,如果达到了阈值,也不Worker ,直接结束
wc >= (core ? corePoolSize : maximumPoolSize)
)
return false;
if (compareAndIncrementWorkerCount(c)) // doug lea 喜欢的做法,1、先改状态 2、再做操作 3、如果失败则回滚
break retry; // CAS 操作成功 则跳出循环,并往下执行 创建Worker
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs) // CAS 操作失败,但快照 rs 和 线程池运行状态不一致,即快照失败了,再循环尝试一次 continue 操作
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
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 {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
runWorker()
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts,对应Worker 构造函数setSate=-1(-1 不能被中断),unlock 之后 容许w被中断 ,非常重要!!!
boolean completedAbruptly = true;
try {
// 1、如果task 不为空则表示是 w的第一个任务,
// 2、如果task 为 空,则从 workerQueue 中取任务
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if (
// 1、runState >= STOP 并且 wt 没有被中断,则会中断wt
// 2、 主线程中断了,并且再次检查runState是>=STOP ,并且 wt没有被中断,则会中断wt
// 3、 如果 1 和 2 都不成立,不需要中断wt,而是去执行wt
(
runStateAtLeast(ctl.get(), STOP) // runState >= STOP
||
(
Thread.interrupted() //
&&
runStateAtLeast(ctl.get(), STOP)
)
)
&&
!wt.isInterrupted()
)
wt.interrupt();
try {
beforeExecute(wt, task); // 预留方法,可以拓展
Throwable thrown = null;
try {
task.run(); // 直接调用task的run方法,执行run的方法体,wt 已经是具备线程的功能,并且是启动了,执行执行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; // task 清楚等待GC回收
w.completedTasks++;
w.unlock(); // 释放w的锁
}
}
completedAbruptly = false;
} finally {
// completedAbruptly = true 有3种场景 :
// 1、 getTask() 抛了异常
// 2、 beforeExecute() 抛了异常,针对自定义线程池会有
// 3、 afterExecute() 抛了异常,针对自定义线程池会有
processWorkerExit(w, completedAbruptly);
}
}
Thread中断相关方法
// 获取线程中断的状态为,并中断线程
public static boolean interrupted() {
return currentThread().isInterrupted(true); // 给 true 会中断线程
}
// 获取中断状态位
public boolean isInterrupted() {
return isInterrupted(false); // 入参给false 不会中断线程
}
// native 方法,参数 ClearInterrupted , 为true 会中断线程,false 不会中断线程
private native boolean isInterrupted(boolean ClearInterrupted);
getTask()
待补充
processWorkerExit()
待补充
tryTerminated()
待补充
拓展线程池
1、如何监控线程池任务执行的异常?
1)、拓展自定义的线程池,task.run 地方进行try catch ,并处理,ThreadPoolExecutor 是甩到 thrown 中了。
2)、拓展自定义的线程池 ,在 (afterExecute(task, thrown); // 预留方法,可以拓展),判断 thrown ,并处理。thrown 捕捉的是 task.run 异常 。
3)、 自定义创建线程池工作线程的工场,并重写newThread(), 并利用Thread#setUncaughtExceptionHandler(), 并自定义 实现UncaughtExceptionHandler 接口
如下是一个示例
public static class JfThreadFactory implements ThreadFactory{
@Override
public Thread newThread(Runnable r){
Thread t = new Thread(r);
t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler(){
@Override
public void uncaughtException(Thread t, Throwable e){
System.out.println("我擦,异常:"+e.getMessage());
}
});
return t;
}
}
public static class JfThreadPool extends ThreadPoolExecutor {
public JfThreadPool(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler){
super(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,threadFactory,handler);
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
// 重新 afterExecute 处理 t 异常
if(null != t){
System.out.println("thrown ="+t.getMessage());
}
// 如果展开下面的异常, uncaughtException 将会捕捉数组越界异常 覆盖 除数为0异常
//int[] arr = new int[3];
//arr[3]=10; // 数组越界异常 Jf
}
}
public static void main(String[] args) {
JfThreadPool pool = new JfThreadPool(1,1,1,TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10),new JfThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
pool.execute( () -> {
System.out.println("1任务执行!!!");
int c = 1/0; // 线程池如何捕获此异常
});
pool.execute( () -> {
int c = 1/0;
System.out.println("任务执行!!!");
});
pool.execute( () -> {
int c = 1/0;
System.out.println("任务执行!!!");
});
}
2、线程任务监控器,为什么会出现丢任务情况,而且啥提示都没有
try{
int c = 1/0;
} // 没有catch {},会吞掉异常
finally{
// 代码
}
finally {
// 没有任何处理,吞了异常没有catch
task = null;
w.completedTasks++;
w.unlock();
}
3、 processWorkerExit(),中捕捉的异常是什么异常,从哪里抛出来的?
是getTask() 抛出的异常,看下图
除了getTask() 抛出异常, completedAbruptly = true ,还有 2处:
其一: beforeExecute()
其二: afterExecute()
// completedAbruptly = true 有3种场景 :
// 1、 getTask() 抛了异常
// 2、 beforeExecute() 抛了异常,针对自定义线程池会有
// 3、 afterExecute() 抛了异常,针对自定义线程池会有
processWorkerExit(w, completedAbruptly);
}
但是(很重要!!!),getTask() 的异常在底层被吞掉了,所以只有用户拓展线程池,并重写 beforeExecute() 或者 afterExecutor() ,而且还写了bug 。 才会导致 completedAbruptly = true。
如下是getTask() 部分代码 和
getTask() 代码片段
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
/**
*
* @param completedAbruptly if the worker died due to user exception
用户导致的worker线程异常,worker计数不会减少
*/
private void processWorkerExit(Worker w, boolean completedAbruptly){
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
// 省略其他代码
}
processWorkerExit() 代码片段 如下
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
}
}
待补充