一 前言
前一篇文章我们学习了Android OkHttp3源码详解——整体框架,今天我们来看一下Dispatcher
(调度器)。首先我们来看看OkHttp的Wiki对Dispatcher`(调度器)介绍。
For synchronous calls, you bring your own thread and are responsible for managing how many simultaneous requests you make. Too many simultaneous connections wastes resources; too few harms latency.
对于同步调用,你用自己的线程负责管理有多少并发请求。太多的并发连接浪费资源;特别是导致延迟。
For asynchronous calls, Dispatcher implements policy for maximum simultaneous requests. You can set maximums per-webserver (default is 5), and overall (default is 64).
对于异步调用,Dispatcher实现最多的并发请求策略。你可以设置每个主机最大请求数(默认为5),和最大并发请求数(默认是64)。
下面就来看Dispatcher(调度器)的源码。
二 Dispatcher(调度器)源码解析
public final class Dispatcher {
/** 最大并发请求数为64 */
private int maxRequests = 64;
/** 每个主机最大请求数为5 */
private int maxRequestsPerHost = 5;
/** 线程池 */
private ExecutorService executorService;
/** 准备执行的请求 */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
/** 正在执行的异步请求,包含已经取消但未执行完的请求 */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
/** 正在执行的同步请求,包含已经取消单未执行完的请求 */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
根据上面代码和思维脑图,很容易看到 Dispatcher
是保存同步和异步Call的地方,并负责执行异步AsyncCall。
Dispatcher使用了一个Deque<AsyncCall> readyAsyncCalls
保存了同步任务;
对于异步请求,Dispatcher使用了两个Deque,分别是Deque<AsyncCall> runningAsyncCalls Deque<RealCall> runningSyncCalls
一个保存准备执行的请求,一个保存正在执行的请求,为什么要用两个呢?因为Dispatcher默认支持最大的并发请求是64个,单个Host最多执行5个并发请求,如果超过,则Call会先被放入到readyAsyncCall中,当出现空闲的线程时,再将readyAsyncCall中的线程移入到runningAsynCalls中,执行请求。
注意:同步请求调用Call的execute()方法
,而异步请求调用call.enqueue()方法
。
我们先来看一下简单点的同步请求。
2.1 Dispatcher(任务调度器) 同步请求
执行同步请求,代码如下(RealCall的execute方法):
@Override public Response execute() throws IOException {
synchronized (this) {
//如果executed等于true,说明已经被执行,如果再次调用执行就抛出异常。这说明了一个Call只能被执行一次。
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
try {
//关键代码在这
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
eventListener.callFailed(this, e);
throw e;
} finally {
client.dispatcher().finished(this);
}
}
首先如果executed等于true,说明已经被执行,如果再次调用执行就抛出异常。这说明了一个Call只能被执行。client.dispatcher().executed(this)
是我们的关键代码,我们来看一下Dispatcher
源码中executed()
方法。
synchronized void executed(RealCall call) {
//正在执行的同步请求,包含已经取消单未执行完的请求
runningSyncCalls.add(call);
}
源码太特妈简单了,就是将这个请求 call
加入到runningSyncCalls
队列中去。
在经过拦截器的处理之后,等一系列操作之后,得到了响应的Response,最终会执行finished()
方法。来看看源码吧。
void finished(RealCall call) {
finished(runningSyncCalls, call, false);
}
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized (this) {
//将请求从队列中移除
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
//同步请求 promoteCalls 为false 不会执行promoteCalls()方法
if (promoteCalls) promoteCalls();
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
对于同步请求,只是简单的将同步请求从runningSyncCalls队列中移除,因为promoteCalls参数是false,因此不会执行promoteCalls方法,
Dispatcher中,同步请求的逻辑还是比较简单的,异步请求的逻辑相对复杂一点,一起来看看吧。
2.2 Dispatcher(任务调度器) 异步请求
异步请求调用RealCall.enqueue()
方法,源码如下,
@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));
}
如果当前的请求没有被执行过,则对当前请求添加监听器等操作,
来看看关键代码 client.dispatcher().enqueue(new AsyncCall(responseCallback))
,首先生成Call(AsyncCall)
对象,将Call(AsyncCall)
对象作为参数传入Dispatcher
的enqueue
方法。来看看Dispatcher
的enqueue()
源码吧。
synchronized void enqueue(AsyncCall call) {
// 执行的请求总数<=64 && 单个Host正在执行的请求<=5
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
// 将请求加入到runningAsyncCalls队列中
runningAsyncCalls.add(call);
// 在线程池中最终会调用AsyncCall的execute()方法
executorService().execute(call);
} else {
// 将请求加入到readyAsyncCalls队列中
readyAsyncCalls.add(call);
}
}
代码注释也非常清楚,我们一起看看吧,如果正在执行的请求总数<=64 则 单个Host正在执行的请求<=5,则将请求加入到runningAsyncCalls队列中,紧接着就是利用线程池执行该请求,否则就将该请求放入readyAsyncCalls队列中。AsyncCall是RealCall的一个内部类并且继承NamedRunnable,NamedRunnable实现了Runnable接口并且是一个抽象类,在线程池中最终会调用AsyncCall的execute()方法执行异步请求。
protected void execute() {
boolean signalledCallback = false;
try {
//拦截器链
Response response = getResponseWithInterceptorChain();
//重试失败,回调onFailure方法
if (retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException e) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
eventListener.callFailed(RealCall.this, e);
responseCallback.onFailure(RealCall.this, e);
}
} finally {
//关键代码 结束
client.dispatcher().finished(this);
}
}
我们主要看client.dispatcher().finished(this)
;其他逻辑和同步方法基本相同。
void finished(AsyncCall call) {
// 注意最后一个参数为true和同步的不一样
finished(runningAsyncCalls, call, true);
}
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized (this) {
//将请求从队列中移除
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
// promoteCalls 为true
if (promoteCalls) promoteCalls();
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
从上面代码中看到最后一个参数是true,这意味着需要执行promoteCalls()
方法。
private void promoteCalls() {
// 正在执行的Call总数超过最大请求值直接返回
if (runningAsyncCalls.size() >= maxRequests) return;
// readyAsyncCalls为空直接返回
if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
// 遍历执行readyRunningCalls的请求
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall call = i.next();
if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
}
if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
}
}
promoteCalls的逻辑也很简单:扫描待执行任务队列,将任务放入正在执行任务队列,并执行该任务。
2.3 Dispatcher(任务调度器) 中线程池(ExecutorService)
OkHttp的一个高效之处在于在内部维护了一个线程池,方便高效地执行异步请求。
1 线程池概念
Android中的线程池的概念来源于Java中的Executor,Executor是一个接口,真正的线程池的实现为ThreadPoolExecutor,ThreadPoolExecutor提供了一系列参数来配置线程池,通过不同的参数可以创建不同的线程池。
2 线程池优点
(1)复用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销。
(2)能够有效的控制线程池的最大并发数,避免大量的线程之间因互相抢占系统资源而导致的阻塞现象。
(3)能够对线程进行简单的管理,并提供定时执行以及指定间隔循环执行等功能。
3 源码
在OkHttp,使用如下方法构造了单例线程池
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;
}
//构造一个线程池ExecutorService:
executorService = new ThreadPoolExecutor(
//corePoolSize 最小并发线程数,如果是0的话,空闲一段时间后所有线程将全部被销毁
0,
//maximumPoolSize: 最大线程数,当任务进来时可以扩充的线程最大值,当大于了这个值就会根据丢弃处理机制来处理
Integer.MAX_VALUE,
//keepAliveTime: 当线程数大于corePoolSize时,多余的空闲线程的最大存活时间
60,
//单位秒
TimeUnit.SECONDS,
//工作队列,先进先出
new SynchronousQueue<Runnable>(),
//单个线程的工厂
Util.threadFactory("OkHttp Dispatcher", false));
构建了一个核心为[0, Integer.MAX_VALUE]线程数的线程池,它不保留任何最小线程数,随时创建更多的线程数,当线程空闲时只能活60秒,它使用了一个不存储元素的阻塞工作队列。
在实际运行中,当收到20个并发请求时,线程池会创建20个线程,当工作完成后,线程池会在60s后相继关闭所有线程。
三 总结
通过上述的分析,我们知道了:
- OkHttp采用Dispatcher(任务调度器)技术,与线程池配合实现了高并发,低阻塞的运行
- Okhttp采用Deque作为缓存,按照入队的顺序先进先出。
- OkHttp最出彩的地方就是在try/finally中调用了finished函数,可以主动控制等待队列的移动,而不是采用锁或者wait/notify,极大减少了编码复杂性。