线程池作为一个线程的容器,主要的作用就是防止频繁创建线程,节省时间资源和cpu资源。虽然一定程度上占用了内存,但实际情况下利远远大于弊。
构造方法
public ThreadPoolExecutor(
int corePoolSize, //核心线程数量
int maximumPoolSize, //最大线程数量
long keepAliveTime, //最大存活时间
TimeUnit unit, //时间单位
BlockingQueue<Runnable> workQueue, //线程队列
ThreadFactory threadFactory, //线程工厂
RejectedExecutionHandler handler) //超过最大线程后,处理方式
上边的是最核心的构造方法,线程池的运作方式其实从构造方法就可以略知一二。我先简述一下线程池的运作方法,以及设置的参数的作用。
- 创建线程池后,其实线程池中线程数量为0;
- 当有一个任务被提交后,会创建一个worker线程,worker会被放入线程池中并开始执行这个任务,任务完成后会等待我们设置的任务队列(workQueue)中的任务。
- 这样不停的提交任务,worker的数量也会不断增多,直到增大到核心线程数量(corePoolSize),此时不会再增加线程池中的worker线程,多出的任务会放入队列中,等到核心线程池中的worker空闲下来再执行放入队列中的任务。如果队列是无限队列可能会出现创建过多线程撑爆内存的现象。
- 当队列满员后,会继续往线程池中增加worker,直到达到线程池最大线程数量(maximumPoolSize)。此时,会根据设置的RejectedExecutionHandler实现类(handler)执行不同的拒绝策略,例如:抛出异常,或者使用当前线程执行。若最大线程数量过大同样会出现worker数量过多撑爆内存的现象。
- 当线程池的worker数量超过核心线程数量时,有worker的任务执行完毕后获取队列中的任务超过指定时间(keepAliveTime和unit确定的时间),这个worker就会被消灭。
- 线程工厂(threadFactory)就是创建线程的地方,可使用Guava的ThreadFactoryBuilder().setNameFormat(“demo-thread-%d”).build()创建threadFactory。也可以使用自带的DefaultThreadFactory。
以上就是线程池运行的大概流程。队列数量过大或者最大线程数量过大,会让服务器挂掉,由于一个服务干爆整个服务器实属不智,应当谨慎使用Executors中的创建方式。
全局变量
下边先看看ThreadPoolExecutor中的全局变量,构造方法中涉及的就不在赘述
//使用了线程安全的AtomicInteger类,保证了ctl的不会因多线程而出现问题
//ctl变量值的前 29 位表示工作线程数量 workerCount, 剩余高位来表示线程池状态runState。
//初始化时默认是running状态,worker数量为0
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//running状态;接受新任务,并处理队列任务,是正常执行状态
private static final int RUNNING = -1 << COUNT_BITS;
//shutdown状态;不接受新任务,但会处理队列任务
private static final int SHUTDOWN = 0 << COUNT_BITS;
//stop状态;不接受新任务,不会处理队列任务,而且会中断正在处理过程中的任务
private static final int STOP = 1 << COUNT_BITS;
//tidying状态;所有的任务已结束,workerCount为0,线程过渡到TIDYING状态,将会执行terminated()钩子方法(此方法为空)
private static final int TIDYING = 2 << COUNT_BITS;
//terminated状态;terminated()方法已经完成后变更成此状态。
private static final int TERMINATED = 3 << COUNT_BITS;
//储存worker的容器,不是线程安全,所以其操作通常需要加锁
private final HashSet<Worker> workers = new HashSet<Worker>();
//全局锁,会保证关键操作不会因并发而混乱,如对workers的操作。
private final ReentrantLock mainLock = new ReentrantLock();
其他全局变量可能并非关键变量,如果遇到会在代码里说明。
下面开始对源码进行分析
execute方法
这是线程池的最核心方法,加入Runnable任务,使用线程池中的worker执行。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
//计算 workerCount 和 runState 时通过掩码计算。
int c = ctl.get();
//若worker数量小于核心线程池中线程数,增加worker,若成功则直接返回。失败继续运行
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//判断若是running状态,尝试向队列中添加任务。运行到此处,核心线程池已满,尝试向队列中添加任务
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
//由于没有加锁,需要重新判断现在是否处于运行状态,若不是需要移除队列中任务,移除后执行插入失败操作reject
if (! isRunning(recheck) && remove(command))
reject(command);
//如果正常需要查看工作的worker数量,若为0,则加入一个空闲Worker。
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//运行到此处说明,核心线程已经饱和,阻塞队列已经满员,尝试再次增加worker环节队列压力
else if (!addWorker(command, false)) 。
reject(command);
}
增加空任务worker的逻辑比较奇怪,可能是状态突然变更为shutdown,队列中的任务已经被加入,shutdown操作意外杀死了所有worker线程,不得不执行这个操作。某种程度上保证了不会出现插入任务却没有worker的情况。根据addWorker方法中判断是否添加worker的逻辑推断得出,尚未完全明白为何会发生这种情况。
addWorker
addWorker方法会尝试向线程池中添加线程。
调用此方法的方法往往不会对线程池状态做出详细判断,所以addWorker方法需要对当前状态做出明确判断,根据不同状态执行不同操作。
入参firstTask表示worker的第一个任务,可能为null,表示只增加worker,不设置初始任务,worker执行的任务从队列中获取;入参core表示是否加入核心线程池
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
/* 判断何时不添加worker直接返回。
* 若线程池状态非running时,一下三个条件都满足才继续运行后边的代码,否则其他非running状态都是值节返回失败
* 1 线程池是shutdown状态 2 创建的worker的任务为空 3 任务队列中有任务未完成
* 由此可以推断出execute加入无任务worker的原因。
*/
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
//判断线程池数量,大于线程池能储存最大数量,直接返回
//判断入参core,如果true线程池数量小于核心线程数,如果false小于设置最大线程数
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//cas增加worker数量,成功跳出,跳过所有循环;cas保证了并发增加worker不会发生异常
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get();
//运行到此处说明ctl改变,若是state改变则跳出到大循环,从新判断;若是worker数量改变则在小循环中继续运行
if (runStateOf(c) != rs)
continue retry;
}
}
//worker是否启动
boolean workerStarted = false;
//woker是否被添加入worker容器
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());
//最后的判断,和mainLock锁一起保证不会发生并发冲突
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
//在这里开始真正增加worker。
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) {
//启动worker线程,worker的run方法会调用runWorker方法,不停的领取任务
t.start();
workerStarted = true;
}
}
} finally {
//worker启动失败,此时调用addWorkerFailed方法。注意,此时ctl中worker数量已经+1,但worker容器中不一定添加。
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
addWorkerFailed
这个方法被调用时,worker因不明原因启动或添加失败。线程池总worker数量加一了。所以需要尝试把worker容器中的启动失败的worker剔除,worker数量减一。
private void addWorkerFailed(Worker w) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (w != null)
workers.remove(w);
decrementWorkerCount();
tryTerminate(); //如有需要尝试关闭线程池
} finally {
mainLock.unlock();
}
}
附上另外两篇ThreadPoolExecutor文章:
https://blog.csdn.net/xiaoyuchenCSDN/article/details/83615323
https://blog.csdn.net/xiaoyuchenCSDN/article/details/83717842