One Step By One Step 解析OkHttp3 - Dispatcher(一)

配合源码食用更佳

概述


使用过OkHttp的童鞋们都知道,同步execute(),异步enqueue()
那么如果做到同步异步的呢,其实我们发送的同步/异步请求都会在Dispatcher中管理其状态
其中维护了:

  • 运行中的异步请求队列 runningAsyncCalls
  • 就绪状态的异步请求队列 readyAsyncCalls
  • 运行中的同步请求队列 runningSyncCalls (注意名字的细微差别)
  • 线程池 executorService

每当有新的同步请求到Dispatcher碗里来,直接加入到同步运行中队列。
每当有新的异步请求到Dispatcher碗里来,那么通过maxRequests(最大请求数)和maxRequestsPerHost(相同host最大请求数)来判定,是应该进到运行中的队列并立即执行呢,还是进到就绪队列中,等待运行队列有空间了,再进到运行队列中。

同时maxRequestsmaxRequestsPerHost都是可调整的,如果往上调了,运行队列可以进更多请求了,那么就可以将就绪状态的请求移动到运行队列中;如果往下调了,如果运行队列中的请求超过了最大请求数,那也没办法,这些超额请求执行完了,就不能再进那么多了。

下面来看源码。

源码


发送同步/异步请求

/**
发送异步请求
此方法为同步方法,因为runningAsyncCalls和readyAsyncCalls使用的ArrayDeque,然而ArrayDeque是非线程安全的,所以需要同步。
如果运行中的异步请求队列的请求数小于最大请求数且当前请求对应的host下对应的请求数小于maxRequestsPerHost,那么就进队列,并且通过线程池立即执行。
*/
synchronized void enqueue(AsyncCall call) {
  // 运行队列中的请求数小于maxRequests且相同host的运行中请求数小于maxRequestsPerHost,下面会贴runningCallsForHost()的代码
  if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
    // 加入到运行中队列
    runningAsyncCalls.add(call);
    // 使用线程池执行请求,下面会贴出executorService初始化的过程。
    executorService().execute(call);
  } else {
    // 加入就绪队列中
    readyAsyncCalls.add(call);
  }
}


/**
  此方法也为同步方法
  直接加入到运行中同步请求队列中
*/
synchronized void executed(RealCall call) {
  //加入到同步运行中队列
  runningSyncCalls.add(call);
}

线程池初始化

/**
  这是一个同步的懒加载线程池的方法
  不了解线程池的童鞋请查阅相关资料
  这里大概的介绍下ThreadPoolExecutor构造器的参数,依顺序来,一个个来
 * corePoolSize 一直存在于线程池中的线程数量(除非allowCoreThreadTimeOut为true)
 * maximumPoolSize 线程池中允许存在的最大线程数量
 * keepAliveTime 除corePoolSize之外的线程,在空闲状态(执行任务完之后)能存在的最大时间
 * unit 上面那货的单位
 * workQueue 通过execute方法发送的任务,会先被缓存在这个队列中
 * threadFactory 创建线程的工厂
*/
public synchronized ExecutorService executorService() {
  //懒加载
  if (executorService == null) {
    //corePoolSize 为 0表示,没有核心线程,所有执行请求的线程,使用完了如果过期了(keepAliveTime)就回收了。
    //maximumPoolSize 无限大的线程池空间
    executorService = new ThreadPoolExecutor(
      0,  //corePoolSize
      Integer.MAX_VALUE, //maximumPoolSize
      60,  //keepAliveTime
      TimeUnit.SECONDS,  //unit
      new SynchronousQueue<Runnable>(),  //workQueue
      Util.threadFactory("OkHttp Dispatcher", false)  //threadFactory
    );
  }
  return executorService;
}

调整请求(就绪/运行)

/**
   根据maxRequests(最大请求数)和maxRequestsPerHost(相同host最大请求数)来调整
   runningAsyncCalls(运行中的异步请求)队列和readyAsyncCalls(就绪状态的异步请求)队列。
   使运行中的异步请求不超过两种最大值,并且如果队列有空闲,将就绪状态的请求归类为运行中。
*/
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();
      //如果当前请求对应的host下,没有超过maxRequestsPerHost,那么将其从就绪队列中移除,并加入在运行队列。
      if (runningCallsForHost(call) < maxRequestsPerHost) {
        //移除
        i.remove();
        //加入运行队列
        runningAsyncCalls.add(call);
        //立即执行该请求
        executorService().execute(call);
      }

      //如果运行队列已经到达了最大请求数上限,如果还有就绪中的请求,也不管了。
      if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
    }
  }

获取同个host下的请求数

/**
    很简单的方法,对比已有的运行中的请求和当前请求的host,相同result++,返回即可
*/
private int runningCallsForHost(AsyncCall call) {
  int result = 0;
  for (AsyncCall c : runningAsyncCalls) {
    if (c.host().equals(call.host())) result++;
  }
  return result;
}

请求结束


/**
    同步请求结束
    当该同步请求结束的时候,会调用此方法,用于将运行中的同步请求队列中的该请求移除
    今后系列中,分析RealCall的时候,会知道何时调用此方法的。
 */
synchronized void finished(Call call) {
  if (!runningSyncCalls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
}

/**
    异步请求结束
    当该异步请求结束的时候,会调用此方法,用于将运行中的异步请求队列中的该请求移除并调整请求队列,此时就绪队列中的请求可以

    今后系列中,分析AsyncCall的时候,会知道何时调用此方法的。
 */
synchronized void finished(AsyncCall call) {
  if (!runningAsyncCalls.remove(call)) throw new AssertionError("AsyncCall wasn't running!");
  promoteCalls();
}

调整运行队列中的最大请求数量

/**
  以下两个方法作用类似,前者是调整总的最大请求数,后者是调整单个host下的最大请求数
  调整之后会有两种情况:
      1. 当前队列中的请求数大于最大请求数:继续执行,将已在队列中的请求执行完成
      2. 当前队列中的请求数小于最大请求数:如过就绪队列中存在请求,将其移动到运行队列中,直到运行队列的大小大于等于对大请求数
*/
public synchronized void setMaxRequests(int maxRequests) {
  // 校验
  if (maxRequests < 1) {
    throw new IllegalArgumentException("max < 1: " + maxRequests);
  }
  // 设置
  this.maxRequests = maxRequests;
  // 调整请求
  promoteCalls();
}

public synchronized void setMaxRequestsPerHost(int maxRequestsPerHost) {
   if (maxRequestsPerHost < 1) {
     throw new IllegalArgumentException("max < 1: " + maxRequestsPerHost);
   }
   this.maxRequestsPerHost = maxRequestsPerHost;
   promoteCalls();
 }

剩余的方法

剩余的方法都是获取一些数据,大家自己看看就好。

总结


Dispatcher的作用为维护请求的状态,并维护一个线程池,用于执行请求。

欢迎交流 QQ:2424334647

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,265评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,274评论 1 288
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,087评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,479评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,782评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,218评论 1 207
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,594评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,316评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,955评论 1 237
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,274评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,803评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,177评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,732评论 3 229
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,953评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,687评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,263评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,189评论 2 258

推荐阅读更多精彩内容