线程池的实现原理
所谓线程池,通俗的理解就是有一个池子,里面存放着已经创建好的线程,当有任务提交给线程池执行时,池子中的某个线程会主动执行该任务。如果池子中的线程数量不够应付数量众多的任务时,则需要自动扩充新的线程到池子中,但是该数量是有限的,就好比池塘的水界线 一样。当任务比较少的时候,池子中的线程能够自动回收,释放资源。为了能够异步地提交任务和缓存未被处理的任务,需要有一个任务队列 |
---|
-----------------------------------------------------------------------------读书笔记摘自书名:Java高并发编程详解:多线程与架构设计 作者:汪文君
线程池处理流程
当向线程池提交一个任务之后,线程池是如何处理这个任务的呢?
前置条件------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 执行步骤 |
---|---|
首先检测 线程池运行状态 | 如果不是RUNNING ,则直接拒绝,线程池要保证在RUNNING 的状态下执行任务 |
① workerCount < corePoolSize | 则创建新线程来执行任务(注意,执行这一步骤需要获取全局锁) 注意: 即使线程池中有空闲线程,也会创建一个新的线程来执行新添加的任务 如果调用了线程池的 prestartAllCoreThreads() 方法,线程池会提前创建并启动所有基本线程workerCount >= corePoolSize 时,如果里面有线程的空闲时间超过了keepAliveTime ,就将其移除线程池,这样可以动态地调整线程池中线程的数量 |
② workerCount >= corePoolSize && 阻塞队列未满 | 将任务加入BlockingQueue |
③ workerCount >= corePoolSize && 阻塞队列已满 && workerCount < maximumPoolSize | 则创建新的线程来处理任务(注意,执行这一步骤需要获取全局锁) |
④ workerCount >= corePoolSize && 阻塞队列已满 && workerCount >= maximumPoolSize | 任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution() 方法 |
ThreadPoolExecutor执行示意图 → (对应处理流程的①②③④)
源码分析
上面的流程分析让我们很直观地了解了线程池的工作原理,让我们再通过源代码来看看是如何实现的,线程池执行任务的方法如下。 |
---|
public void execute(Runnable command) {
if (command == null) throw new NullPointerException();
// 如果线程数小于基本线程数,则创建线程并执行当前任务
if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
// 如线程数大于等于基本线程数或线程创建失败,则将当前任务放到工作队列中。
if (runState == RUNNING && workQueue.offer(command)) {
if (runState != RUNNING || poolSize == 0) ensureQueuedTaskHandled(command);
}
// 如果线程池不处于运行中或任务无法放入队列,并且当前线程数量小于最大允许的线程数量,
// 则创建一个线程执行任务。
else if (!addIfUnderMaximumPoolSize(command))
// 抛出RejectedExecutionException异常
reject(command); // is shutdown or saturated
}
}
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
ThreadPoolExecutor执行任务示意图
工作线程
线程池创建线程时,会将线程封装成工作线程Worker,Worker在执行完任务后,还会循环获取工作队列里的任务来执行。我们可以从Worker类的run()方法里看到这点。 |
---|
public void run() {
try {
Runnable task = firstTask;
firstTask = null;
while (task != null || (task = getTask()) != null) {
runTask(task);
task = null;
}
} finally {
workerDone(this);
}
}
线程池中的线程执行任务分两种情况,如下。 |
---|
1)在 execute() 方法中创建一个线程时,会让这个线程执行当前任务。 |
2)这个线程执行完上图中 1 的任务后,会反复从 BlockingQueue 获取任务来执行。 |
-----------------------------------------------------------------------------读书笔记摘自 书名:Java并发编程的艺术 作者:方腾飞;魏鹏;程晓明