okhttp篇2:Dispatcher

news2024/11/23 11:37:08

Dispatchers维护着一个线程池,3个双端队列,准备执行的AsynCall,正在执行的AsynCall,正在执行的同步Call(RealCall)。

同时规定每个Host最多同时请求5个Request,同时可最多执行64个Request。

public final class Dispatcher {
  private int maxRequests = 64;// 同时执行的最大请求数64
  private int maxRequestsPerHost = 5;// 同一个host最大可同时执行的Request个数
  private @Nullable Runnable idleCallback;

  /** Executes calls. Created lazily. */
  private @Nullable ExecutorService executorService;

  /** 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<>();

  /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
}

Dispatcher的线程池

线程池回忆

先回忆一波线程池的运行:

  1. 核心线程池:一开始会根据task创建最多核心线程池个线程,超过核心线程数的task会被放入阻塞队列。如果设置了allowCoreThreadTimeout=true,则核心线程池在获取task执行超过keepAliveTime时间,线程数会减少,否则核心线程数个线程会一直存在,阻塞等待workQueue中来task。
  2. 最大线程池:如果task超过阻塞队列,则会开始创建线程,直到最大线程池个线程。
  3. keepAliveTime:控制获取task的时间,超过这个时间还获取不到task执行,将会减少线程数目。所以变相等于线程存活的时间。
  4. workQueue(BlockingQueue):workQueue的类型决定了当
  5. ThreadFactory:创建线程,决定线程名的策略。

Dispatcher默认的线程池规定

public synchronized ExecutorService executorService() {
  if (executorService == null) {
      // keepAliveTime为60s。线程最多等待60s,没有任务则销毁。
    executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
        new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
  }
  return executorService;
}
  1. 没有核心线程,并且最大线程数为MAX_VALUE,并且使用的阻塞队列为SynchronousQueue,即有task提交的时候,如果有空闲线程,则使用空闲线程,否则新创建一个线程来执行task。并且线程在等待blockingqueue.take方法keepAliveTime,即这里的60s之后,没有task之后,线程将会被销毁。

setMaxRequests

maxRequests指的是同时运行的最大Requests的个数。

设置maxRequests的同时,会从readyAsyncCalls中取call放入runningAsyncCalls和放入线程池中执行。直到正在执行的runningAsynCall的个数>=maxRequests。

取出的call要满足<maxRequestPerHost。

一个host默认最多同时发起5个Request,总共可同时发起64个Request。

public synchronized void setMaxRequests(int maxRequests) {
  if (maxRequests < 1) {
    throw new IllegalArgumentException("max < 1: " + maxRequests);
  }
  this.maxRequests = maxRequests;
  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();
    // 如果call的host正在执行的Requests个数<maxRequestPerHost,就将这个call加入执行队列
    if (runningCallsForHost(call) < maxRequestsPerHost) {
      i.remove();
      runningAsyncCalls.add(call);
      executorService().execute(call);
    }

    if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
  }
}
// 获取正在执行的异步call的host与传入的call host相同的个数,即发起call的host已经有多少个Request在执行
private int runningCallsForHost(AsyncCall call) {
  int result = 0;
  for (AsyncCall c : runningAsyncCalls) {
    if (c.host().equals(call.host())) result++;
  }
  return result;
}

这个promoteCalls方法主要作用就是如果检查到runningAsyncCalls中有空缺(<64),就从readyAsyncCalls中取出一个Call丢到runningAsyncCalls,和丢到线程池中去执行。

这个方法除了会被setMaxRequests调用,还会在AsyncCall执行完成之后调用的finshed调用(这部分后面会讲到)。

enqueue

enqueue接收的参数是AsyncCall。AsyncCall实质是一个Runnable,实现了Runnable接口。

enqueue方法主要检查是否可以让AsyncCall直接执行。如果runningSyncCalls队列还有空位(runningAsyncCalls.size < maxRequests),并且AsyncCall所属的host还没有满员,就可以直接执行。

否则,加入readyAsyncCalls队列中。

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

executed

// 这里executed只是将Call加入同步队列,但是在RealCall方法里的execute会直接调用getResponse方法,那里才是真正获取Response的地方
synchronized void executed(RealCall call) {
  runningSyncCalls.add(call);// 
}

executed方法接收的参数是一个RealCall。由于executed方法代表的是同步执行,所以这里只是将Call加入了队列。具体的执行会在RealCall中被调用(详情看后面的RealCall篇介绍)。

setIdleCallback

设置当dispatchers空闲时候的回调,即当runningSyncCalls和runningAsyncCalls都为空时调用。

/**
 * Set a callback to be invoked each time the dispatcher becomes idle (when the number of running
 * calls returns to zero).
 *
 * <p>Note: The time at which a {@linkplain Call call} is considered idle is different depending
 * on whether it was run {@linkplain Call#enqueue(Callback) asynchronously} or
 * {@linkplain Call#execute() synchronously}. Asynchronous calls become idle after the
 * {@link Callback#onResponse onResponse} or {@link Callback#onFailure onFailure} callback has
 * returned. Synchronous calls become idle once {@link Call#execute() execute()} returns. This
 * means that if you are doing synchronous calls the network layer will not truly be idle until
 * every returned {@link Response} has been closed.
 */
public synchronized void setIdleCallback(@Nullable Runnable idleCallback) {
  this.idleCallback = idleCallback;
}

触发时机是finished方法,finished方法会在RealCall / AsyncCall执行完成的时候调用。 

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();// runningSyncCalls.size + runningAsynCalls.size
    idleCallback = this.idleCallback;
  }

  if (runningCallsCount == 0 && idleCallback != null) {
    idleCallback.run();
  }
}

总结

  1. Dispatcher管理着一个线程池,3个calls,分别是同步正在执行的calls:runningSyncCalls,异步正在执行的calls:runningAsyncCalls,正在排队的异步calls,readyAsyncCalls。没有正在排队的同步calls,因为同步calls都是马上执行的(唯一有一个问题,没有看到同步call到底在哪里执行,异步call好歹调用了excutorService.execute了)。
  2. Dispatchers的线程池配置为:没有核心线程池,最大线程数为MAX_VALUE,同步队列为synchronousQueue,keepAliveTime为60s,这代表,只要有task,如果有空闲线程,就用空闲线程执行,否则会创建新的线程去执行task。当线程获取task超过60s,还没有task,这个线程会被销毁。
  3. Dispatchers还另外管理着两个重要的参数,maxRequests:代表同时执行的最大请求数,maxRequestsPerHost:一个Host最多同时请求的数目,设置maxRequestsPerHost是为了资源的均匀利用,避免一个host发过多请求,阻塞其他的host。
  4. Dispatchers提供自动补全maxRequests个request的功能,当调用setMaxRequests或finished方法的时候,如果runningAsyncCalls的个数不足maxRequests,会自动补全。

Dispatcher.finished方法,会在AsyncCall执行完成之后被调用。通过这个就可以实现,当线程池中任务执行完后,Dispatcher会及时将Runnable丢到线程池去执行。

Dispatcher是管理Runnable(AsyncCall)的,线程池也是管理Runnable的,那为什么Dispatcher不干脆来一个Call,就丢一个Call进线程池执行呢?反正线程池自己也会调用Runnable的执行。

因为Dispatcher想要控制一个host最多一次发5个Request,做到资源的均匀利用,才会在线程池外部又自己做了一套Runnable的管理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/530806.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

玩转Google开源C++单元测试框架Google Test系列(gtest)之一 - 初识gtest

一、前言 本篇将介绍一些gtest的基本使用&#xff0c;包括下载&#xff0c;安装&#xff0c;编译&#xff0c;建立我们第一个测试Demo工程&#xff0c;以及编写一个最简单的测试案例。 二、下载 如果不记得网址&#xff0c; 直接在google里搜gtest&#xff0c;第一个就是。目…

Docker下Gitlab配置Let’s Encrypt证书

Docker下Gitlab配置Let’s Encrypt证书 1 参考文档2 常见问题2.1 前置条件2.2 不支持ip2.3 重复签发2.4 外网无法访问 ※3 内网穿透配置&#xff08;可选&#xff09;4 Gitlab 创建并配置Let’s Encrypt证书4.1 开放Let’s Encrypt签发所需端口4.2 新增存储HTTPS证书文件夹4.3 …

多态与虚函数(补)

多态与虚函数&#xff08;补&#xff09; 静态联编与动态联编的深层次理解多态底层原理 示例示例一示例二示例三示例四 对象与内存虚析构函数构造函数为什么不能是虚函数&#xff1f; 静态联编与动态联编的深层次理解 我们首先看下面一段代码 class object { private: int va…

C learning_12 操作符前篇(算术操作符、移位操作符、位操作符、赋值操作符、单目操作符、关系操作符、逻辑操作符)

目录 算术操作符 移位操作符 移位规则 位操作符 交换两个整形变量的写法 赋值操作符 单目操作符 sizeof和数组的纠缠 和--运算符 多组输入的方案 关系操作符 逻辑操作符 算术操作符 -- 加法操作符&#xff08;&#xff09;&#xff1a;用于将两个值相加。 -- 减法操…

Python爬虫(二):Requests库

所谓爬虫就是模拟客户端发送网络请求&#xff0c;获取网络响应&#xff0c;并按照一定的规则解析获取的数据并保存的程序。要说 Python 的爬虫必然绕不过 Requests 库。 1 简介 对于 Requests 库&#xff0c;官方文档是这么说的&#xff1a; Requests 唯一的一个非转基因的 P…

存储知识点:RAID0、RAID1、RAID5、RAID10特点是什么?所需的硬盘数量分别为多少?

RAID&#xff08;Redundant Array of Independent Disks&#xff09;是一种将多个独立的硬盘组合成一个逻辑磁盘的技术&#xff0c;目的是提高性能或容错能力。RAID有不同的级别&#xff0c;常见的有RAID0、RAID1、RAID5、RAID10等。下面我们来介绍这些级别的特点和所需的硬盘数…

套接字编程简介

作者&#xff1a;V7 博客&#xff1a;https://www.jvmstack.cn 一碗鸡汤 少年辛苦终身事&#xff0c;莫向光阴惰寸功。 —— 杜荀鹤 Socket概述 在计算机中产生和接受IO流的数据源是多种多样的&#xff0c;在网络编程中&#xff0c;有一个特殊的数据源就是socket。通俗点soc…

linux的系统日志

目录 一、日志文件的产生 二、日志文件存放在哪儿 &#xff08;1&#xff09;文本日志 &#xff08;2&#xff09;二进制日志 三、日志存放规则的配置文件 四、日志轮转 五、分析和监控日志 一、日志文件的产生 日志内容&#xff1a;内核、开机引导、守护进程启动运行的…

华为和思科两种常见的网络设备如何进行ospf配置?

概述 ospf&#xff08;开放最短路径优先&#xff09;是一种基于链路状态的动态路由协议&#xff0c;它可以在网络中自动发现和维护最优的路由路径。ospf广泛应用于大型和复杂的网络环境&#xff0c;因为它具有以下优点&#xff1a; 支持分层路由&#xff0c;可以将网络划分为…

WebAssembly黑暗的一面

案例1&#xff1a;技术支持诈骗 什么是技术支持诈骗&#xff1f; 技术支持诈骗是一种电话欺诈&#xff0c;其中诈骗者声称可以提供合法的技术支持服务。该骗局可能以陌生电话开始&#xff0c;骗子通常会声称来自合法的第三方的员工&#xff0c;如“微软”或“Windows部门”。他…

YOLOv5实现目标分类计数并显示在图像上

有同学后台私信我&#xff0c;想用YOLOv5实现目标的分类计数&#xff0c;因此本文将在之前目标计数博客的基础上添加一些代码&#xff0c;实现分类计数。阅读本文前请先看那篇博客&#xff0c;链接如下&#xff1a; YOLOv5实现目标计数_Albert_yeager的博客 1. 分类实现 以co…

web 实验一 HTML基本标签实验

实验原理 通过创建HTML5网页&#xff0c;验证form内多种元素标签及其属性的作用及意义。 实验目的 理解并掌握Form表单提交必须声明的内容 理解并掌握Input元素中多种类型属性的使用方法及使用场景 理解并掌握Label元素的使用方法 理解并掌握Datalist元素的使用方法 理解并掌握…

软件测试学习——笔记一

一、软件和软件测试 1、软件和软件分类 &#xff08;1&#xff09;软件&#xff1a;程序、数据、文档——用户手册 &#xff08;2&#xff09;软件的分类 按层次划分&#xff1a;系统软件、应用软件按组织划分&#xff1a;开源软件&#xff08;代码公开&#xff09;、商业软…

RSA 加密算法在C++中的实现 面向初学者(附代码)

概述 博文的一&#xff0c;二部分为基础知识的铺垫。分别从密码学&#xff0c;数论两个方面为理解RSA算法做好了准备。第三部分是对RSA加密过程的具体介绍&#xff0c;主要涉及其密钥对&#xff08;key-pair&#xff09;的获取。前三个部分与编程实践无关&#xff0c;可以当作…

C# | 内存池

内存池 文章目录 内存池前言什么是内存池内存池的优点内存池的缺点 实现思路示例代码结束语 前言 在上一篇文章中&#xff0c;我们介绍了对象池的概念和实现方式。对象池通过重复利用对象&#xff0c;避免了频繁地创建和销毁对象&#xff0c;提高了系统的性能和稳定性。 今天我…

你真的了解索引吗

当我们学习存储算法和索引算法时&#xff0c;他们可以深入了解如何在系统中存储和查询数据。因为存储和查询数据是许多系统的核心功能之一&#xff0c;例如数据库、搜索引擎等。理解这些算法可以帮助程序员更好地设计和优化系统架构&#xff0c;提高系统的可扩展性、可用性和性…

玩转Google开源C++单元测试框架Google Test系列(gtest)之二 - 断言

一、前言 这篇文章主要总结gtest中的所有断言相关的宏。 gtest中&#xff0c;断言的宏可以理解为分为两类&#xff0c;一类是ASSERT系列&#xff0c;一类是EXPECT系列。一个直观的解释就是&#xff1a; 1. ASSERT_* 系列的断言&#xff0c;当检查点失败时&#xff0c;退出当前…

大数据之光:Apache Spark 实用指南 大数据实战详解【上进小菜猪大数据】

上进小菜猪&#xff0c;沈工大软件工程专业&#xff0c;爱好敲代码&#xff0c;持续输出干货。 本文将深入探讨Apache Spark作为一种强大的大数据处理框架的基本概念、特点和应用。我们将详细介绍Spark的核心组件&#xff0c;包括Spark Core、Spark SQL、Spark Streaming和Spa…

百子作业 —— 中国邮递员问题

题目 严老师和宋老板去勘测武威市区的道路网&#xff0c;每一条路都需要勘测&#xff0c;且需要两人合作.武威市区可以近似地看成六横六纵组成的道路网&#xff0c;自西向东依次为学府路、民勤路、西关路、中关路、富民路、滨河路&#xff1b;自北向南依次为雷海路、宣武路、祁…

Redis基本数据类型及使用(2)

书接上回&#xff0c;这节讲讲其余的基本数据结构使用 集合&#xff0c;有序集合以及遍历和事务的使用 Set集合&#xff0c;无序不重复的成员 表现形式&#xff1a; key1string1string2key2string1string2 常用的基本操作&#xff1a; sadd key string1 [string2..]添加1…