前言
OkHttp的任务队列在内部维护了一个线程池用于执行具体的网络请求,而线程池最大的好处在于通过线程复用减少非核心任务的损耗。
线程池的优点
多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力。但如果对多线程应用不当,会增加对单个任务的处理时间。可以举一个简单的栗子:
假设在一台服务器完成一项任务的时间为T
T1 创建线程的时间
T2 在线程中执行任务的时间,包括线程间同步所需时间
T3 线程销毁的时间
显然 T = T1 + T2 + T3。
可以看出T1, T3是多线程本身带来的开销(在Java中是,通过映射pThread,并进一步通过SystemCall实现native线程),我们渴望减少T1,T3所用的时间,从而减少T的时间。但一些线程的使用者并没有注意到这一点,所以在程序中频繁的创建或销毁线程,这导致T1和T3在T中占有相当比例。显然这是突出了线程的弱点,而不是优点(并发性)。
线程池技术正是关注如何缩短或调整T1,T3时间的技术,从而提高服务器程序性能的。
- 通过对线程进行缓存,减少了创建销毁线程的时间损失
- 通过控制线程数量阈值,减少了当线程过少时带来的CPU闲置与线程过多时对JVM的内存与线程切换时系统调用的压力
类似的还有Socket连接池、DB连接池、CommonPool(比如Jedis)等技术。
OkHttp的任务队列
OkHttp的任务队列主要由两部分组成:
- 任务分发器dispatcher:负责为任务找到合适的执行线程
- 网络请求任务线程池
|
|
|
|
可以看出,在OkHttp中,构建了一个阈值为[0 ,Integer.MAX_VALUE]的线程池,它不保留任何最小线程数,随时创建更多的线程数,当线程空闲时只能活60秒,它使用了一个不存储元素的阻塞工作队列,一个叫做“OkHttp Dispatcher”的线程工厂。
Dispathcer分发器
dispatcher分发器通过Dispathcer将任务分发到合适的空闲线程,实现非阻塞,高可用,高并发连接。
同步请求
当我们使用OkHttp进行同步请求时,实际上是调用了RealCall的execute方法。
|
|
同步调用的执行步骤是:
- 将对应任务加入分发器
- 执行任务
- 执行完成后通知dispatcher对应任务已完成,对应任务出队
异步请求
使用OkHttp进行异步请求时,先会调用RealCall的enqueue方法。
|
|
而当HttpClient请求入队时,根据代码,可以发现实际上是Dispathcer进行了Dispathcer入队操作。
|
|
接下来看看AsyncCall的execute方法。
|
|
当任务执行完成后,无论成功与否都会调用dispathcer的finished方法,通知分发器相关任务已结束。
|
|
接下来看看promoteCalls方法。
|
|
promoteCalls的逻辑也很简单,扫描待执行任务队列,将任务放入正在执行任务队列,并执行该任务。
小结
以上就是整个任务队列的实现细节,总结起来有以下几个特点:
- OkHttp采用Dispatcher技术,类似于Nginx,与线程池配合实现了高并发,低阻塞的运行
- OkHttp采用Deque作为缓存,按照入队的顺序先进先出
- OkHttp最出彩的地方就是在try / finally中调用了finished函数,可以主动控制等待队列的移动,而不是采用锁或者wait / notify,极大减少了编码复杂性