前言
在看 Okhttp 源码的时候,看到有关线程池的使用,先看看调用链:
OkHttp 框架的简单使用代码示例:
// 代码段0
// OkHttpActivity.java
// 第一,新建客户端
OkHttpClient okHttpClient = new OkHttpClient();
/// 第二,构建请求
final Request request = new Request.Builder()
.url(url) // 想该 url 发送请求
.get() //默认就是GET请求,可以不写
.build();
// 第三,新建Call
Call call = okHttpClient.newCall(request);
// 第四,发起调用
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d(TAG, "onFailure: ");
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.d(TAG, "onResponse: " + response.body().string());
}
});
看第四点,进入源码,
goto: call.enqueue(new Callback(){...})
// 代码段5
// RealCall.java
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
goto: client.dispatcher().enqueue(new AsyncCall(responseCallback))
// 代码段6
// okhttp-3.11.0-sources.jar!/okhttp3/
// Dispatcher.java
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
goto executorService()
// 代码段7
// okhttp-3.11.0-sources.jar!/okhttp3/
// Dispatcher.java
public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
由此可知,OkHttp 框架使用了可缓存线程池:CachedThreadPool。
为什么使用这种线程池?这种线程池有什么特点?
先说结论,再去看原理。
// 各个参数的含义:
new ThreadPoolExecutor(
0, // 核心线程数,int corePoolSize
Integer.MAX_VALUE, // 最大线程数,int maximumPoolSize
60, // 非核心线程的闲置超时时间,long keepAliveTime
TimeUnit.SECONDS, // 时间单位(配合上一个参数),TimeUnit unit
new SynchronousQueue<Runnable>(), // 阻塞队列 BlockingQueue<Runnable>
Util.threadFactory("OkHttp Dispatcher", false) // 线程工厂 ThreadFactory,用来给线程设置名字,一般可省略。
)
对于线程池的理解,可以简单的看看 刘望舒,线程池
为什么使用 CachedThreadPool,引用 刘望舒,线程池 中的一段:
CachedThreadPool在程序执行时会创建与所需数量相同的线程,然后在它回收旧线程时停止创建新线程,因此是Executor的首选,只有当这种方式会引发问题,或者不符合业务需要时才采用另外的三种Executor提供的线程池
正文
核心函数 execute(Runnable command)
接着代码段6,
goto executorService().execute(call);
// 代码段8
// Android/sdk/sources/android-28/java/util/concurrent/
// ThreadPoolExecutor.java
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) { //8-1
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) { //8-2
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0) //8-2.5
addWorker(null, false);
}
else if (!addWorker(command, false)) //8-3
reject(command); //8-4
}
8-1处,判断如果 「当前工作的线程数」 < 「核心线程数」,
通过 addWorker(command, true)
立马新建线程去执行任务,然后 return;
⚠️ addWorker 函数的第一个参数 command,表示直接执行该任务,不需要从阻塞队列中取;
⚠️ addWorker 函数的第二个参数为 true,表示受「核心线程数」的限制,即增加核心线程去处理任务。
8-2处,执行到此处,说明 「当前工作的线程数」 >= 「核心线程数」;
这时候,先通过 `workQueue.offer(command)` 让任务入队;
入队成功后,等待「**未来某个线程进入空闲状态**」就来处理它即可;
但以防万一,,在 8-2.5 处再执行一次判断,若当前工作的线程数 == 0,则通过 `addWorker(null, false)` 建线程,保证至少有一个线程,最终会执行到刚入队的任务。
⚠️ addWorker 函数的第一个参数为 null,表示要从阻塞队列中取任务;
⚠️ addWorker 函数的第二个参数为 false,表示受「最大线程数」的限制,即增加非核心线程去处理任务。
8-3处,若执行到此处,说明任务入队失败,「阻塞队列已满」;
此时的情况是:「当前工作的线程数」 >= 「核心线程数」,而且 「阻塞队列已满」;
这时候会再通过 `addWorker(command, false)` 增加非核心线程去处理当前 command 这个任务。
8-4处,若执行到此处,说明 8-3处 增加非核心线程失败; 进一步可知,「当前工作的线程数」>= 「最大线程数」,也有可能是线程池不是「RUNNING状态」; 最后通过 `reject(command)` 语句,来拒绝该任务。
**_上述逻辑就是线程池的核心逻辑,其他成员函数和成员变量都是为该逻辑服务的。_**
引用该链接 深入理解Java线程池:ThreadPoolExecutor 的一段总结:
简单来说,在执行execute()方法时如果状态一直是RUNNING时,的执行过程如下:
1,如果workerCount < corePoolSize,则创建并启动一个线程来执行新提交的任务;
2,如果workerCount >= corePoolSize,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中;
3,如果workerCount >= corePoolSize && workerCount < maximumPoolSize,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务;
4,如果workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常。
引用该链接 深入理解Java线程池:ThreadPoolExecutor 的一张图来解析 execute(Runnable command) 函数:
**再来一个不严谨的简单概括:** 假设:corePoolSize = 10,maximumPoolSize = 1000;
在这种情况下,一般线程池最多只派出 10 个线程,来处理任务,这 10 个线程也叫「核心线程」;
所以「核心线程」的另一个含义就是可以一直存在。
而对于来不及处理任务,就排队等待;
如果队列满了,又再来任务,才会在「核心线程」之外新建「非核心线程」,专门来处理排不上队的任务;
并且,「核心线程」数 +「非核心线程」数 即 = 线程总数 <= maximumPoolSize。
只让 10 个「核心线程」一直存在的原因是节省内存空间,节省资源;有些任务所需的处理时间很短,排一下队,让 10 个「核心线程」逐个处理就好了,不会耽误很长时间;否则如果同时进来一万个任务,就开一万个线程,那将会是非常浪费内存和资源的。
打个形象的比方,银行窗口(线程)就开那么几个,但却一堆人(任务)在排队办业务。
新建线程
接着看 addWorker 函数的内部如何实现:
goto addWorker(command, true)
,addWorker(null, false)
,addWorker(command, false)
// 代码段9
// ThreadPoolExecutor.java
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);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 尝试让线程数加1
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
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 {
// 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;
}
compareAndIncrementWorkerCount(c)
该语句,让 c 加 1,即让线程数加 1;
t.start()
该语句,启动 Worker 实例中的 线程;
接着看 Worker 类的源码:
goto class Worker
// 代码段10
// ThreadPoolExecutor.java
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
......
// 创建时会同步创建一个线程
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
/** Delegates main run loop to outer runWorker. */
public void run() {
runWorker(this);
}
......
}
可以看到,this.thread = getThreadFactory().newThread(this);
该语句,在创建 Worker 实例时,其构造函数同时创建一个线程,并把引用交给自身的成员变量 thread ,并将 Worker 实例自身 this 传给 thread,所以,只要执行该 thread 的 start() 函数,即可进入子线程,在子线程中执行到该 Worker 实例的 run 函数,进而调用 runWorker(this)。
在子线程中执行任务
goto runWorker(this)
// 代码段11
// ThreadPoolExecutor.java
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
//11-1 在此处调用了 getTask() !
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 ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run(); //11-2 执行了任务
} 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;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
注意,该函数的执行是在子线程中,即 Worker 实例的成员变量 thread 所引用的线程中!
先关注11-1处,该语句 while (task != null || (task = getTask()) != null)
,
满足第一个条件 task != null
,则不会执行第二个条件的语句 (task = getTask()) != null
;
若满足第一个条件,意味着是从 代码段8 的 8-1 addWorker(command, true)
或 8-3 addWorker(command, false)
的语句过来的;
否则,就是从 代码段8 的 8-2.5 addWorker(null, false)
的语句过来的。
接着可以看到11-2处,task.run();
该语句,执行了开发者提交给线程池的任务!
其中,getTask()
这是获取任务的作用。
最终,要走这里 processWorkerExit(w, completedAbruptly)
。
先看 getTask() 函数:
goto getTask()
// 代码段12
// ThreadPoolExecutor.java
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
这两处就是从队列取任务的:
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)
workQueue.take()
整个 线程池 类,就只有此处有取任务的代码。
收尾工作
再回到代码段13,finally 会走 processWorkerExit 函数。
goto processWorkerExit(w, completedAbruptly)
// 代码段13
// ThreadPoolExecutor.java
private void processWorkerExit(Worker w, boolean completedAbruptly) {
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
workers.remove(w);
} finally {
mainLock.unlock();
}
tryTerminate();
int c = ctl.get();
if (runStateLessThan(c, STOP)) { //13-1
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);
}
}
if (runStateLessThan(c, STOP))
,表示线程池没有 STOP
,
那就会通过 addWorker(null, false)
来新增线程,取队列的任务执行。
到此可知,线程是不断回收和新建的,只是数量一只保持在「核心线程」数之内,
这就产生了一直有固定几个线程实例在运行的错觉。
总结
本文不严谨的只关注到其中的一些主要逻辑,忽略了很多细节;
更详细的内容可以参考该文章,非常详细,可多读几遍:
深入理解Java线程池:ThreadPoolExecutor
关于一些状态量和位运算,可以参考:
Java并发之线程池ThreadPoolExecutor源码分析学习
最后对重要函数做一个总结:
- execute(Runnable command)
- 决定进来的任务:立刻由核心员工(线程)接待,还是排队等待
- 若排不上队,再由非核心员工(线程)接待
- 若员工(线程)总数超标,则拒绝任务
- addWorker(Runnable firstTask, boolean core)
- 检查相关条件
- 新建线程,并更新相关变量
- 启动线程
- runWorker(Worker w)
- 该步骤起,进入 子线程执行
- 接手当前任务,或者从队列取任务
getTask()
; - 执行任务
- processWorkerExit(Worker w, boolean completedAbruptly)
- 执行完任务的收尾工作
- 保证还有一定数量的线程去处理队列的任务
- 若线程不足就新增线程,即
addWorker(null, false)