OkHttp3源码详解(六)Okhttp任务队列工作原理
1 概述
1.1 引言
android
完成非阻塞式的异步请求的时候都是通过启动子线程的方式来解决,子线程执行完任务的之后通过handler
的方式来和主线程来完成通信。无限制的创建线程,会给系统带来大量的开销。如果在高并发的任务下,启用个线程池,可以不断的复用里面不再使用和有效的管理线程的调度和数量的管理。就可以节省系统的成本,有效的提高执行效率。
1.2 线程池ThreadPoolExecutor
okhttp的线程池对象存在于Dispatcher类中。实例过程如下
1. public synchronized ExecutorService executorService() { 2. if (executorService == null) { 3. executorService = , Integer.MAX_VALUE, , TimeUnit.SECONDS, 4. new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false)); 5. } 6. return executorService; 7. }
1.2 Call对象
了解源码或使用过okhttp
的都知道。 okttp
的操作元是Call对象。异步的实现是RealCall.AsyncCall
。而 AsyncCall
是实现的一个Runnable
接口。
1. final class AsyncCall extends NamedRunnable {}
所以Call本质就是一个Runable
线程操作元肯定是放进excutorService中直接启动的。
2 线程池的复用和管理
2.1 图解
为了完成调度和复用,定义了两个队列分别用作等待队列和执行任务的队列。这两个队列都是Dispatcher
成员变量。Dispatcher是一个控制执行,控制所有Call的分发和任务的调度、通信、清理等操作。这里只介绍异步调度任务。
1. /** Ready async calls in the order they'll be run. */ 2. private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>(); 4. /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */ 5. private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
在《okhttp连接池复用机制》文章中我们在缓存Connection连接的时候也是使用的Deque双端队列。这里同样的方式,可以方便在队列头添加元素,移除尾部的元素。
2.2 过程分析
Call
代用equeue
方法的时候
1. synchronized void enqueue(AsyncCall call) { 2. if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { 3. runningAsyncCalls.add(call); 4. executorService().execute(call); 5. } else { 6. readyAsyncCalls.add(call); 7. } 8. }
方法中满足执行队列里面不足最大线程数maxRequests
并且Call对应的host数目不超过maxRequestsPerHost
的时候直接把call对象直接推入到执行队列里,并启动线程任务(Call
本质是一个Runnable
)。否则,当前线程数过多,就把他推入到等待队列中。Call
执行完肯定需要在runningAsyncCalls
队列中移除这个线程。那么readyAsyncCalls
队列中的线程在什么时候才会被执行呢。
追溯下AsyncCall
线程的执行方法
1. @Override 2. protected void execute() { 3. boolean signalledCallback = false; 4. try { 5. Response response = getResponseWithInterceptorChain(forWebSocket); 6. if (canceled) { 7. signalledCallback = true; 8. responseCallback.onFailure(RealCall.this, new IOException("Canceled")); 9. } else { 10. signalledCallback = true; 11. responseCallback.onResponse(RealCall.this, response); 12. } 13. } catch (IOException e) { 14. if (signalledCallback) { 15. // Do not signal the callback twice! 16. Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e); 17. } else { 18. responseCallback.onFailure(RealCall.this, e); 19. } 20. } finally { 21. client.dispatcher().finished(this); 22. } 23. } 24. }
这里做了核心request的动作,并把失败和回复数据的结果通过responseCallback
回调到Dispatcher。执行操作完毕了之后不管有无异常都会进入到dispactcher
的finished
方法。
1. private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) { 2. int runningCallsCount; 3. Runnable idleCallback; 4. synchronized (this) { 5. if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!"); 6. if (promoteCalls) promoteCalls(); 7. runningCallsCount = runningCallsCount(); 8. idleCallback = this.idleCallback; 9. } 11. && idleCallback != null) { 12. idleCallback.run(); 13. } 14. }
在这里call在runningAsyncCalls队列中被移除了,重新计算了目前正在执行的线程数量。并且调用了promoteCalls()
看来是来调整任务队列的,跟进去看下
1. private void promoteCalls() { 2. if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity. 3. if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote. 5. for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) { 6. AsyncCall call = i.next(); 8. if (runningCallsForHost(call) < maxRequestsPerHost) { 9. i.remove(); 10. runningAsyncCalls.add(call); 11. executorService().execute(call); 12. } 14. if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity. 15. } 16. }
原来实在这里对readyAsyncCalls
进行调度的。最终会在readyAsyncCalls
中通过remove
操作把元素迭代取出并移除之后加入到runningAsyncCalls的执行队列中执行操作。ArrayDeque
是非线程安全的所以finished
在调用promoteCalls
的时候都在synchronized
块中执行的。执行等待队列线程当然的前提是runningAsyncCalls
线程数没有超上线,而且等待队列里面有等待的任务。
以上完成了线程线程池的复用和线程的管理工作。
小结,Call在执行任务通过Dispatcher把单元任务优先推到执行队列里进行操作,如果操作完成再执行等待队列的任务。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
OkHttp3源码详解(四)缓存策略
合理地利用本地缓存可以有效地减少网络开销,减少响应延迟。HTTP报头也定义了很多与缓存有关的域来控制缓存。今天就来讲讲OkHttp中关于缓存部分的实现细节。 HTTP缓存策略首先来了解下HTTP协议中缓存部分的相关域。 1.1 Expires超时时间,一般用在服务器的response报头中用于告知客户端对应资源的过期时间。当客户端需要再次请求相同资源时先比较其过期时间,如果尚未超过过期时间则直接返回缓存结果,如果已经超过则重新请求。 1.2 Cache-Control相对值,单位时秒,表示当前资源的有效期。Cache-Control比Expires优先级更高: Cache-Control:max-age=31536000,public1.3 条件GET请求1.3.1 Last-Modified-Date客户端第一次请求时,服务器返回: Last-Modified: Tue, 12 Jan 2016 09:31:27 GMT当客户端二次请求时,可以头部加上如下header: If-Modified-Since: Tue, 12 Jan 2016 09:31:27 GMT如果当前资源没有被...
- 下一篇
Android 桌面角标二三事
我们广大用户对于通知消息栏和桌面角标都很熟悉,Google Android 是在 8.0 以后加入的,但是对于部分国内厂商较早就有尝试,小菜今天对桌面角标进行简单尝试; 华为厂商 对于桌面角标的兼容处理,华为是最明确最容易处理的,官方文档 清晰明了,小菜按照官方介绍尝试如下: 集成方式 1. 权限声明 <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="com.huawei.android.launcher.permission.CHANGE_BADGE" /> 2. 设置基本参数并将角标数传递给桌面应用 public static void setHuaweiBadge(Context context, int count) { try { Bundle badgeBundle = new Bundle(); badgeBundle.putString("package", "包名"); badgeBundle.putStri...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS8编译安装MySQL8.0.19
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS8安装Docker,最新的服务器搭配容器使用
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- SpringBoot2整合Redis,开启缓存,提高访问速度
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS关闭SELinux安全模块
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程