1 概述

1.1 引言

  android完成非阻塞式的异步请求的时候都是通过启动子线程的方式来解决,子线程执行完任务的之后通过handler的方式来和主线程来完成通信。无限制的创建线程,会给系统带来大量的开销。如果在高并发的任务下,启用个线程池,可以不断的复用里面不再使用和有效的管理线程的调度和数量的管理。就可以节省系统的成本,有效的提高执行效率。

1.2 线程池ThreadPoolExecutor

okhttp的线程池对象存在于Dispatcher类中。实例过程如下

public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(, Integer.MAX_VALUE, , TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}

1.2 Call对象

了解源码或使用过okhttp的都知道。 okttp的操作元是Call对象。异步的实现是RealCall.AsyncCall。而 AsyncCall是实现的一个Runnable接口。

final class AsyncCall extends NamedRunnable {}

所以Call本质就是一个Runable线程操作元肯定是放进excutorService中直接启动的。

2 线程池的复用和管理

2.1 图解

为了完成调度和复用,定义了两个队列分别用作等待队列和执行任务的队列。这两个队列都是Dispatcher 成员变量。Dispatcher是一个控制执行,控制所有Call的分发和任务的调度、通信、清理等操作。这里只介绍异步调度任务。

/** Ready async calls in the order they'll be run. */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>(); /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

《okhttp连接池复用机制》文章中我们在缓存Connection连接的时候也是使用的Deque双端队列。这里同样的方式,可以方便在队列头添加元素,移除尾部的元素。

2.2 过程分析

Call代用equeue方法的时候

synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}

方法中满足执行队列里面不足最大线程数maxRequests并且Call对应的host数目不超过maxRequestsPerHost 的时候直接把call对象直接推入到执行队列里,并启动线程任务(Call本质是一个Runnable)。否则,当前线程数过多,就把他推入到等待队列中。Call执行完肯定需要在runningAsyncCalls 队列中移除这个线程。那么readyAsyncCalls队列中的线程在什么时候才会被执行呢。

追溯下AsyncCall 线程的执行方法

@Override
protected void execute() {
boolean signalledCallback = false;
try {
Response response = getResponseWithInterceptorChain(forWebSocket);
if (canceled) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException e) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
}

这里做了核心request的动作,并把失败和回复数据的结果通过responseCallback 回调到Dispatcher。执行操作完毕了之后不管有无异常都会进入到dispactcherfinished方法。

private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized (this) {
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
if (promoteCalls) promoteCalls();
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
} if (runningCallsCount == && idleCallback != null) {
idleCallback.run();
}
}

在这里call在runningAsyncCalls队列中被移除了,重新计算了目前正在执行的线程数量。并且调用了promoteCalls() 看来是来调整任务队列的,跟进去看下

private void promoteCalls() {
if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote. for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall call = i.next(); if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
} if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
}
}

原来实在这里对readyAsyncCalls 进行调度的。最终会在readyAsyncCalls 中通过remove操作把元素迭代取出并移除之后加入到runningAsyncCalls的执行队列中执行操作。ArrayDeque 是非线程安全的所以finished在调用promoteCalls 的时候都在synchronized块中执行的。执行等待队列线程当然的前提是runningAsyncCalls 线程数没有超上线,而且等待队列里面有等待的任务。

以上完成了线程线程池的复用和线程的管理工作。

小结,Call在执行任务通过Dispatcher把单元任务优先推到执行队列里进行操作,如果操作完成再执行等待队列的任务。

												

OkHttp3源码详解(六) Okhttp任务队列工作原理的更多相关文章

  1. OkHttp3源码详解(五) okhttp连接池复用机制

    1.概述 提高网络性能优化,很重要的一点就是降低延迟和提升响应速度. 通常我们在浏览器中发起请求的时候header部分往往是这样的 keep-alive 就是浏览器和服务端之间保持长连接,这个连接是可 ...

  2. OkHttp3源码详解(一) Request类

    每一次网络请求都是一个Request,Request是对url,method,header,body的封装,也是对Http协议中请求行,请求头,实体内容的封装 public final class R ...

  3. OkHttp3源码详解(三) 拦截器

    1.构造Demo 首先构造一个简单的异步网络访问Demo: OkHttpClient client = new OkHttpClient(); Request request = new Reques ...

  4. OkHttp3源码详解(三) 拦截器-RetryAndFollowUpInterceptor

    最大恢复追逐次数: ; 处理的业务: 实例化StreamAllocation,初始化一个Socket连接对象,获取到输入/输出流()基于Okio 开启循环,执行下一个调用链(拦截器),等待返回结果(R ...

  5. OkHttp3源码详解(二) 整体流程

    1.简单使用 同步: @Override public Response execute() throws IOException { synchronized (this) { if (execut ...

  6. 源码详解系列(六) ------ 全面讲解druid的使用和源码

    简介 druid是用于创建和管理连接,利用"池"的方式复用连接减少资源开销,和其他数据源一样,也具有连接数控制.连接可靠性测试.连接泄露控制.缓存语句等功能,另外,druid还扩展 ...

  7. 源码详解系列(八) ------ 全面讲解HikariCP的使用和源码

    简介 HikariCP 是用于创建和管理连接,利用"池"的方式复用连接减少资源开销,和其他数据源一样,也具有连接数控制.连接可靠性测试.连接泄露控制.缓存语句等功能,另外,和 dr ...

  8. Spark Streaming揭秘 Day25 StreamingContext和JobScheduler启动源码详解

    Spark Streaming揭秘 Day25 StreamingContext和JobScheduler启动源码详解 今天主要理一下StreamingContext的启动过程,其中最为重要的就是Jo ...

  9. spring事务详解(三)源码详解

    系列目录 spring事务详解(一)初探事务 spring事务详解(二)简单样例 spring事务详解(三)源码详解 spring事务详解(四)测试验证 spring事务详解(五)总结提高 一.引子 ...

随机推荐

  1. Linux 网络工具详解之 ip tuntap 和 tunctl 创建 tap/tun 设备

    本文首发于我的公众号 Linux云计算网络(id: cloud_dev),专注于干货分享,号内有 10T 书籍和视频资源,后台回复「1024」即可领取,欢迎大家关注,二维码文末可以扫. 在前面一篇文章 ...

  2. Anaconda下载地址

    Anaconda installer archive:地址1: https://repo.continuum.io/archive/地址2:https://mirrors.tuna.tsinghua. ...

  3. ES6之Promise的基本用法

    之前多次看过阮一峰的ES6教程,对Promise也简单的理解过,但是,由于没在项目中运用过,所以记忆的并不深刻,昨天在进行项目的改良,有一个地方需要用到Promise 所以就这样写了: onload函 ...

  4. Ocelot简易教程(四)之请求聚合以及服务发现

    上篇文章给大家讲解了Ocelot的一些特性并对路由进行了详细的介绍,今天呢就大家一起来学习下Ocelot的请求聚合以及服务发现功能.希望能对大家有所帮助. 作者:依乐祝 原文地址:https://ww ...

  5. MySQL(4)---慢查询

    慢查询 简介       开启慢查询日志,可以让MySQL记录下查询超过指定时间的语句,通过定位分析性能的瓶颈,才能更好的优化数据库系统的性能. 一.配置慢查询 1.参数说明 slow_query_l ...

  6. MySQL导入导出实践

    最近一次数据迁移,需要将MySQL的数据导出.处理后导入到新表和ES.这里做个简单记录,方便后续查询. 注: 为了写文章方便及隐私安全,实际内容会有所简化.例如表结构简化.数据库连接部分全部用 xxx ...

  7. SpringBoot(12) SpringBoot创建非web应用

    在Spring Boot中,要创建一个非Web应用程序,实现CommandLineRunner并覆盖run()方法 @SpringBootApplication public class Spring ...

  8. #2 Python面向对象(一)

    前言 对于萌新来说,面向对象,这是一个很陌生的词语.如果你接触过Linux,你一定知道“一切皆文件”,也就是说,在Linux中所有都是文件,鼠标是文件.键盘是文件.目录是文件等等一切都是文件:Pyth ...

  9. 第12章 Linux配置定时任务详解

    12.1 配置定时任务 首先需弄清的概念: (1).crond是一个daemon类程序,路径为/usr/sbin/crond.默认会以后台方式启动,service或systemd方式启动crond默认 ...

  10. 【golang-GUI开发】qt之signal和slot(二)

    上一篇文章里我们详细介绍了signal的用法. 今天我们将介绍slot的使用.在qt中slot和signal十分相像,这次我们将实现一个能显示16进制数字的SpinBox,它继承自QSpinbox并重 ...