这里写目录标题
一级目录
二级目录
三级目录
线程池 ThreadPoolExecutor
线程池概述:利用池化技术提高线程的利用率,减少创建和销毁的开销
- 降低资源的消耗,通过利用已经创建的线程,降低线程创建和销魂造成的消耗
- 提高响应速度,当任务到达时,可以从线程池中获取已有的线程,节省线程创建的时间
- 提高线程的可管理性,线程不能无限创建,使用线程池可以进行统一的分配,调优和监控
使用Executor来启动线程比使用start方法好,除了更方便管理,效率更好(节省开销)外,还有一个关键优点:有助于避免this逃逸问题
注:this逃逸是指在构造函数返回之前其他线程就持有对该对象的引用,调用尚未构建完全的对象的方法容易引发未知的错误
线程池主要内容:
- 线程池的管理
- 线程工厂
- 线程队列
- 拒绝策略
Executor框架结构
- 任务 (Runable / Callable):任务实际执行的类,需要实现Runable或Callable接口
- 任务执行(Executor)
- 异步计算结果(Future)
线程池执行线程流程:
- 主线程创建实现Runable或Callable接口的任务对象
- 把创建完成的实现 Runable或Callable接口的对象直接交给ExecutorService执行:
ExecutorService.execute(Runable commoand) 或ExecutorService.submit(Runable task)或者ExecutorService.submit(Callable task) - 如果执行ExecutorService.submit() 方法,Executor将返回一个实现Future接口的对象。
- 最后主线程执行FutureTask.get()方法等待线程执行完毕,获取线程执行结束后的返回值。FutureTask.cancel() 可以用来取消任务的执行
ThreedPoolExecutor 类初始化代码:
/**
* 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;
}
ThreadPoolExecutor饱和策略定义(任务拒绝策略):
如果当前同时执行的线程数量达到最大线程数数量,并且队列也已经被任务放满,那么对于新增进来的任务,ThreadPoolExecutor定义了以下几种策略:
- ThreadPoolExecutor.AbortPolicy: 抛出RejectedExecutionException异常来拒绝新任务的处理。默认情况下使用该策略
- ThreadPoolExecutor.CallerRunsPolicy: caller runs policy 调用执行自己的线程(一般为主线程或上一级线程)来执行任务。这个时候因为需要自己执行任务,而不是用一个“新”线程异步执行,会降低对于新任务的提交速度,影响整体性能。该策略会对队列进行扩容,如果程序能够容忍延迟,并且不能丢弃每个任务,那么可以采取这个策略。
- ThreadPoolExecutor.DiscardPolicy: 不做处理任务,直接丢掉该任务
- ThreadPoolExecutor.DiscardOldsetPolicy: 将队列里面最早的任务丢弃掉
线程池原理分析
execute()源码解析:关键点:线程池的状态、核心线程数、队列已有线程数。
// 存放线程池的运行状态 (runState) 和线程池内有效线程的数量 (workerCount)
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static int workerCountOf(int c) {
return c & CAPACITY;
}
//任务队列
private final BlockingQueue<Runnable> workQueue;
public void execute(Runnable command) {
// 如果任务为null,则抛出异常。
if (command == null)
throw new NullPointerException();
// ctl 中保存的线程池当前的一些状态信息
int c = ctl.get();
// 下面会涉及到 3 步 操作
// 1.首先判断当前线程池中之行的任务数量是否小于 corePoolSize
// 如果小于的话,通过addWorker(command, true)新建一个线程,并将任务(command)添加到该线程中;然后,启动该线程从而执行任务。
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
// 2.如果当前之行的任务数量大于等于 corePoolSize 的时候就会走到这里
// 通过 isRunning 方法判断线程池状态,线程池处于 RUNNING 状态才会被并且队列可以加入任务,该任务才会被加入进去
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// 再次获取线程池状态,如果线程池状态不是 RUNNING 状态就需要从任务队列中移除任务,并尝试判断线程是否全部执行完毕。同时执行拒绝策略。
if (!isRunning(recheck) && remove(command))
reject(command);
// 如果当前线程池为空就新创建一个线程并执行。
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//3. 通过addWorker(command, false)新建一个线程,并将任务(command)添加到该线程中;然后,启动该线程从而执行任务。
//如果addWorker(command, false)执行失败,则通过reject()执行相应的拒绝策略的内容。
else if (!addWorker(command, false))
reject(command);
}
网上找的图:可以清洗地看出一个任务从被提交至线程池到执行的流程
addWorker()方法用来创建一个新的工作线程,返回true时说明创建启动成功,否则返回false:
// 全局锁,并发操作必备
private final ReentrantLock mainLock = new ReentrantLock();
// 跟踪线程池的最大大小,只有在持有全局锁mainLock的前提下才能访问此集合
private int largestPoolSize;
// 线程集合,存放线程池中所有的(活跃的)线程,只有在持有全局锁mainLock的前提下才能访问此集合
private final HashSet<Worker> workers = new HashSet<>();
//获取线程池状态
private static int runStateOf(int c) { return c & ~CAPACITY; }
//判断线程池的状态是否为 Running
private static boolean isRunning(int c) {
return c < SHUTDOWN;
}
/**
* 添加新线程到线程池
* @param firstTask 要执行
* @param core参数为true的话表示使用线程池的基本大小,为false使用线程池最大大小
* @return 成功返回true失败返回false
*/
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
//来获取线程池的状态
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
//获取线程池中线程的数量
int wc = workerCountOf(c);
// core参数为true的话表明队列也满了,线程池大小变为 maximumPoolSize
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//原子操作将workcount的数量加1
if (compareAndIncrementWorkerCount(c))
break retry;
// 如果线程的状态改变了就再次执行上述操作
c = ctl.get();
if (runStateOf(c) != rs)
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 {
//获取线程池状态
int rs = runStateOf(ctl.get());
//rs < SHUTDOWN 如果线程池状态依然为RUNNING,并且线程的状态是存活的话,就会将工作线程添加到工作线程集合中
//(rs=SHUTDOWN && firstTask == null)如果线程池状态小于STOP,也就是RUNNING或者SHUTDOWN状态下,同时传入的任务实例firstTask为null,则需要添加到工作线程集合和启动新的Worker
// firstTask == null证明只新建线程而不执行任务
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();
}
如果成功添加工作线程,则调用Worker内部的线程实例t的Thread#start()方法启动真实的线程实例
if (workerAdded) {
t.start();
/// 标记线程启动成功
workerStarted = true;
}
}
} finally {
// 线程启动失败,需要从工作线程中移除对应的Worker
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
动态池化技术
美团技术团队的博客:https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html