总结
- okhttp
默认同时支持 64 个异步请求(不考虑同步请求),一个 host 同时最多请求 5 个 - okhttp 内部的
线程池都是 CacheThreadPool:核心线程数为 0,非核心线程数无限,永远添加不到等待队列中 okhttpClient 如果不单例,会出现 oom:因为大量的 Dispatcher 对象,不同的对象会使用不同的线程去发起网络请求,从而导致线程过多,OOM
清理连接线程池
OkHttp 会对连接 ( 简单理解为 Socket ) 进行复用,以减少 tcp+https 握手时间。但也不能对所有连接都复用,连接在空闲一段时间后必须要被清理掉以节约资源。这就需要 ConnectionPool 对连接进行管理
ConnectionPool 有一个静态的线程池,会由它执行清理过期连接任务
连接最多被缓存 5 个,每一个最多空闲 5 分钟
处理请求线程池
我们知道 okhttp 可以支持异步请求,因此它 必然有一个线程池用于发起请求。以一个很简单的例子深入
RealCall#enqueue() 如下:
Dispatcher#enqueue() 很简单:
// Dispatcher.java
void enqueue(AsyncCall call) {
synchronized (this) {
readyAsyncCalls.add(call);// 先记录
}
// 然后调用方法
promoteAndExecute();
}
promoteAndExecute 如下:
上面的关键点就是 executorService() 的返回值,它就是 Okhttp 网络请求的线程池
这里面最关注的就是传入的队列,它是 SynchronousQueue,它的 offer() 方法有一个很神奇的现象:只是单纯调用 offer() 时永远不会入队成功;再结合线程池的 execute() 方法就可以知道:这个线程池会无限创建非核心线程去执行任务。
当然,这并不是说该方法会在瞬间发起多个请求时会 oom。因为它上面有一个同步的代码块,保证了最多只有 64(默认) 个任务执行
AsyncCall 拿到 ExecutorService 后,会在线程池内执行自己 —— 它本身就是一个 Ruunable,主要逻辑:调用 getResponseWithInterceptorChain(),然后执行各种拦截器,最终拿到返回结果,然后回调设置的 callback
当请求执行完成后,会调用 Dispatcher#finish() 方法,该方法会调用 promoteAndExecute(),从而取出下一个任务开始执行
同步
说到这里再说一下同步请求的处理逻辑。更简单
线程池导致的 oom
核心原因:OkHttpClient 未单例。如果短时间内大量创建 client,必然会大量创建 Dispatcher,在使用 client 发起异步网络请求时,就会大量创建线程池,然后 oom 了
一个 Dispatcher 最大支持 64 个异步请求,但因为大量不同的 Dispatcher,所以这个 64 并没有什么用处:
// OkHttpClient.Builder 构造函数
dispatcher = new Dispatcher();