Dispatcher
Dispatcher 是一个用于调度网络请求的组件,它负责将请求分发到不同的线程或线程池中执行。
Dispatcher 的主要作用是:
线程管理:Dispatcher 负责管理网络请求的线程,它可以将请求分发到不同的线程池中执行,以实现并发处理多个请求。
任务调度:Dispatcher 可以根据请求的优先级、类型或者其他的调度策略,决定在哪个线程上执行网络请求。
线程隔离:通过使用不同的 Dispatcher,OKHTTP 可以确保网络请求在特定的线程上执行,避免阻塞主线程,提高应用的响应性和性能。
并发控制:Dispatcher 可以控制同时执行的网络请求数量,避免过多请求导致系统资源耗尽。
在OKHTTP中,Dispatcher 通常与 Dispatcher.Main、Dispatcher.IO、Dispatcher.Default 等预定义的调度器一起使用,这些调度器提供了不同的线程池,用于执行不同的任务。例如,Dispatcher.IO 通常用于执行后台的网络请求,而 Dispatcher.Main 用于执行与UI相关的操作。
最大并发执行请求数 64
每个域名并发执行的最大请求数 5
下面看一下最重要的几个方法以及配置变量。
class Dispatcher() {
//最大并发执行请求数
var maxRequests = 64
get() = this.withLock { field }
set(maxRequests) {
require(maxRequests >= 1) { "max < 1: $maxRequests" }
this.withLock {
field = maxRequests
}
promoteAndExecute()
}
//每个域名并发执行的最大请求数
var maxRequestsPerHost = 5
get() = this.withLock { field }
set(maxRequestsPerHost) {
require(maxRequestsPerHost >= 1) { "max < 1: $maxRequestsPerHost" }
this.withLock {
field = maxRequestsPerHost
}
promoteAndExecute()
}
var idleCallback: Runnable? = null //每次调度程序空闲时(当正在运行的调用数量返回零时)调用的回调。
private val readyAsyncCalls = ArrayDeque<AsyncCall>()
private val runningAsyncCalls = ArrayDeque<AsyncCall>()
private val runningSyncCalls = ArrayDeque<RealCall>()
}
可以看到 promoteAndExecute() 是出现频率最高的一方法,特别是在set()方法之后,那 promoteAndExecute() 里面到底干了什么呢
//将符合条件的调用从readyAsyncCalls提升到runningAsyncCalls,并在执行程序服务上运行它们
//上面的所有成员变量以及对象几乎都会在下面的方法中被调用
//主要是使用ArrayDeque负责每个call的ready-running状态的切换,
private fun promoteAndExecute(): Boolean {
lock.assertNotHeld() // 断言当前线程没有持有锁
val executableCalls = mutableListOf<AsyncCall>() // 创建一个可执行的异步调用列表
var isRunning: Boolean // 标记是否还有正在运行的调用
this.withLock { // 获取锁,确保线程安全
val i = readyAsyncCalls.iterator() // 获取readyAsyncCalls的迭代器
while (i.hasNext()) { // 遍历readyAsyncCalls
val asyncCall = i.next() // 获取下一个异步调用
if (runningAsyncCalls.size >= this.maxRequests) break // 如果当前运行的异步调用数量达到最大限制,则跳出循环
if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // 如果当前主机已经达到最大请求限制,则跳过该调用
i.remove() // 从readyAsyncCalls中移除该调用
asyncCall.callsPerHost.incrementAndGet() // 增加该主机调用的计数
executableCalls.add(asyncCall) // 将调用添加到executableCalls列表
runningAsyncCalls.add(asyncCall) // 将调用添加到runningAsyncCalls列表
}
isRunning = runningCallsCount() > 0 // 标记是否有正在运行的调用
}
// 如果executorService已经被关闭,则取消执行并减少计数
if (executorService.isShutdown) {
for (i in 0 until executableCalls.size) {
val asyncCall = executableCalls[i]
asyncCall.callsPerHost.decrementAndGet()
this.withLock {
runningAsyncCalls.remove(asyncCall)
}
asyncCall.failRejected() // 调用失败处理
}
idleCallback?.run() // 如果存在空闲回调,则执行
} else {
for (i in 0 until executableCalls.size) {
val asyncCall = executableCalls[i]
asyncCall.executeOn(executorService)
}
}
return isRunning // 返回是否有正在运行的调用
}
首先,它获取一个锁,以确保在多线程环境中操作队列时的线程安全。
然后,它遍历readyAsyncCalls队列,选择可以执行的调用(即当前运行的异步调用数量没有达到最大限制,且每个主机的调用数量也没有达到最大限制)。
如果executorService已经关闭,它会取消这些调用并减少计数,然后执行相应的失败处理。
如果executorService没有关闭,它会执行这些调用。
最后,它返回一个布尔值,表示是否有正在运行的调用。
使用
val client = OkHttpClient.Builder()
.dispatcher(Dispatcher().apply {
maxRequests = 32 // 设置同时执行的最大请求数量
maxRequestsPerHost = 5 // 设置每个主机的最大请求数量
})
.build()