OKHttp源码解析(三)——分发器Dispatcher - Go语言中文社区

OKHttp源码解析(三)——分发器Dispatcher


学习参考资料:OKHttp源码解析OKHttp源码分析——拦截器OKhttp完全解析-拦截器

一、概括

说到OKHttp请求的同步和异步,就要提到Dispatcher分发器了,根据前两篇的源码分析,可以知道在发起请求时,整个框架主要通过Call来封装每一次的请求。同时Call持有OkHttpClient和一份HttpEngine。而每一次的同步或者异步请求都会有Dispatcher的参与,不同的是:

同步

Dispatcher会在同步执行任务队列中记录当前被执行过得任务Call,同时在当前线程中去执行Call的getResponseWithInterceptorChain()方法,直接获取当前的返回数据Response;

异步

首先来说一下Dispatcher,Dispatcher内部实现了懒加载无边界限制的线程池方式,同时该线程池采用了 SynchronousQueue这种阻塞队列。SynchronousQueue每个插入操作必须等待另一个线程的移除操作,同样任何一个移除操作都等 待另一个线程的插入操作。因此此队列内部其 实没有任何一个元素,或者说容量是0,严格说并不是一种容器。由于队列没有容量,因此不能调用peek操作,因为只有移除元素时才有元素。显然这是一种快 速传递元素的方式,也就是说在这种情况下元素总是以最快的方式从插入者(生产者)传递给移除者(消费者),这在多任务队列中是最快处理任务的方式。对于高 频繁请求的场景,无疑是最适合的。

异步执行是通过Call.enqueue(Callback responseCallback)来执行,在Dispatcher中添加一个封装了Callback的Call的匿名内部类Runnable来执行当前 的Call。这里一定要注意的地方这个AsyncCall是Call的匿名内部类。AsyncCall的execute方法仍然会回调到Call的 getResponseWithInterceptorChain方法来完成请求,同时将返回数据或者状态通过Callback来完成。

二、源码分析

OkHttp的任务队列主要由两部分组成:
1. 任务分发器dispatcher:负责为任务找到合适的执行线程  2.网络请求任务线程池

Dispatcher

readyCalls:待执行异步任务队列

runningCalls:运行中异步任务队列

executedCalls:运行中同步任务队列

executorService:任务队列线程池

ThreadPoolExecutor

(PS:这里附上的代码为OKHttp2.5.0,后面有改动的源码,会附上3.7版本的代码,整体并不影响学习)

int corePoolSize: 最小并发线程数,这里并发同时包括空闲与活动的线程,如果是0的话,空闲一段时间后所有线程将全部被销毁

int maximumPoolSize: 最大线程数,当任务进来时可以扩充的线程最大值,当大于了这个值就会根据丢弃处理机制来处理

long keepAliveTime: 当线程数大于corePoolSize时,多余的空闲线程的最大存活时间,类似于HTTP中的Keep-alive

TimeUnit unit: 时间单位,一般用秒

BlockingQueue workQueue: 工作队列,先进先出,可以看出并不像Picasso那样设置优先队列

ThreadFactory threadFactory: 单个线程的工厂,可以打Log,设置Daemon(即当JVM退出时,线程自动结束)等

构建ThreadPoolExecutor

可以看出,在Okhttp中,构建了一个阀值为[0, Integer.MAX_VALUE]的线程池,它不保留任何最小线程数,随时创建更多的线程数,当线程空闲时只能活60秒,它使用了一个不存储元素的阻塞工作队列,一个叫做"OkHttp Dispatcher"的线程工厂。

也就是说,在实际运行中,当收到10个并发请求时,线程池会创建十个线程,当工作完成后,线程池会在60s后相继关闭所有线程。

分发器执行图

同步:

OkHttpClient client =newOkHttpClient();Requestrequest=newRequest.Builder()

.url("http://publicobject.com/helloworld.txt")

.build();Responseresponse= client.newCall(request).execute();

其中最后的call.execute();我们来看一下同步中的execute()方法:

(PS:代码为3.7版本)

RealCall.execute

重点为:

client.dispatcher().executed(this);

client.dispatcher().finished(this);

同步调用的执行逻辑是:1.将对应任务加入分发器 2.执行任务 3.执行完成后通知dispatcher对应任务已完成,对应任务出队

异步:

OkHttpClient client =newOkHttpClient();Requestrequest=newRequest.Builder()

.url("http://publicobject.com/helloworld.txt")

.build();

client.newCall(request).enqueue(newCallback() {

@Overridepublicvoid onFailure(Callcall, IOException e) {Log.d("OkHttp","Call Failed:"+ e.getMessage());

}

@Overridepublicvoid onResponse(Callcall,Responseresponse) throws IOException {Log.d("OkHttp","Call succeeded:"+response.message());

}

});

异步中的call.enqueue(new Callback(){})

当HttpClient的请求入队时,根据代码,我们可以发现实际上是Dispatcher进行了入队操作。

如果满足条件:

当前请求数小于最大请求数(64)

对单一host的请求小于阈值(5)

将该任务插入正在执行任务队列,并执行对应任务。如果不满足则将其放入待执行队列。

从之前的笔记中已经看过AsyncCall的execute()方法了

execute

当任务执行完成后,无论成功与否都会调用dispatcher.finished方法,通知分发器相关任务已结束:

finish

空闲出多余线程,调用promoteCalls调用待执行的任务

如果当前整个线程池都空闲下来,执行空闲通知回调线程(idleCallback)

接下来看看promoteCalls:

promoteCalls

promoteCalls的逻辑也很简单:扫描待执行任务队列,将任务放入正在执行任务队列,并执行该任务。

三、 总结

以上就是整个任务队列的实现细节,总结起来有以下几个特点:

OkHttp采用Dispatcher技术,类似于Nginx,与线程池配合实现了高并发,低阻塞的运行

Okhttp采用Deque作为缓存,按照入队的顺序先进先出

OkHttp最出彩的地方就是在try/finally中调用了finished函数,可以主动控制等待队列的移动,而不是采用锁或者wait/notify,极大减少了编码复杂性

讲解及图片来源OKHttp源码分析——任务队列

版权声明:本文来源简书,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://www.jianshu.com/p/a99e58a4aeaf
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-01-08 08:40:14
  • 阅读 ( 1551 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢