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

配合源碼食用更佳

概述


使用過OkHttp的童鞋們都知道,同步execute()咐容,異步enqueue()舆逃。
那么如果做到同步異步的呢,其實(shí)我們發(fā)送的同步/異步請求都會在Dispatcher中管理其狀態(tài)戳粒。
其中維護(hù)了:

  • 運(yùn)行中的異步請求隊(duì)列 runningAsyncCalls
  • 就緒狀態(tài)的異步請求隊(duì)列 readyAsyncCalls
  • 運(yùn)行中的同步請求隊(duì)列 runningSyncCalls (注意名字的細(xì)微差別)
  • 線程池 executorService

每當(dāng)有新的同步請求到Dispatcher碗里來路狮,直接加入到同步運(yùn)行中隊(duì)列。
每當(dāng)有新的異步請求到Dispatcher碗里來享郊,那么通過maxRequests(最大請求數(shù))和maxRequestsPerHost(相同host最大請求數(shù))來判定,是應(yīng)該進(jìn)到運(yùn)行中的隊(duì)列并立即執(zhí)行呢孝鹊,還是進(jìn)到就緒隊(duì)列中炊琉,等待運(yùn)行隊(duì)列有空間了,再進(jìn)到運(yùn)行隊(duì)列中又活。

同時maxRequestsmaxRequestsPerHost都是可調(diào)整的苔咪,如果往上調(diào)了,運(yùn)行隊(duì)列可以進(jìn)更多請求了柳骄,那么就可以將就緒狀態(tài)的請求移動到運(yùn)行隊(duì)列中团赏;如果往下調(diào)了,如果運(yùn)行隊(duì)列中的請求超過了最大請求數(shù)耐薯,那也沒辦法舔清,這些超額請求執(zhí)行完了,就不能再進(jìn)那么多了曲初。

下面來看源碼体谒。

源碼


發(fā)送同步/異步請求

/**
發(fā)送異步請求
此方法為同步方法,因?yàn)閞unningAsyncCalls和readyAsyncCalls使用的ArrayDeque臼婆,然而ArrayDeque是非線程安全的抒痒,所以需要同步。
如果運(yùn)行中的異步請求隊(duì)列的請求數(shù)小于最大請求數(shù)且當(dāng)前請求對應(yīng)的host下對應(yīng)的請求數(shù)小于maxRequestsPerHost颁褂,那么就進(jìn)隊(duì)列故响,并且通過線程池立即執(zhí)行傀广。
*/
synchronized void enqueue(AsyncCall call) {
  // 運(yùn)行隊(duì)列中的請求數(shù)小于maxRequests且相同host的運(yùn)行中請求數(shù)小于maxRequestsPerHost,下面會貼runningCallsForHost()的代碼
  if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
    // 加入到運(yùn)行中隊(duì)列
    runningAsyncCalls.add(call);
    // 使用線程池執(zhí)行請求,下面會貼出executorService初始化的過程彩届。
    executorService().execute(call);
  } else {
    // 加入就緒隊(duì)列中
    readyAsyncCalls.add(call);
  }
}


/**
  此方法也為同步方法
  直接加入到運(yùn)行中同步請求隊(duì)列中
*/
synchronized void executed(RealCall call) {
  //加入到同步運(yùn)行中隊(duì)列
  runningSyncCalls.add(call);
}

線程池初始化

/**
  這是一個同步的懶加載線程池的方法
  不了解線程池的童鞋請查閱相關(guān)資料
  這里大概的介紹下ThreadPoolExecutor構(gòu)造器的參數(shù)伪冰,依順序來,一個個來
 * corePoolSize 一直存在于線程池中的線程數(shù)量(除非allowCoreThreadTimeOut為true)
 * maximumPoolSize 線程池中允許存在的最大線程數(shù)量
 * keepAliveTime 除corePoolSize之外的線程惨缆,在空閑狀態(tài)(執(zhí)行任務(wù)完之后)能存在的最大時間
 * unit 上面那貨的單位
 * workQueue 通過execute方法發(fā)送的任務(wù)糜值,會先被緩存在這個隊(duì)列中
 * threadFactory 創(chuàng)建線程的工廠
*/
public synchronized ExecutorService executorService() {
  //懶加載
  if (executorService == null) {
    //corePoolSize 為 0表示,沒有核心線程坯墨,所有執(zhí)行請求的線程寂汇,使用完了如果過期了(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;
}

調(diào)整請求(就緒/運(yùn)行)

/**
   根據(jù)maxRequests(最大請求數(shù))和maxRequestsPerHost(相同host最大請求數(shù))來調(diào)整
   runningAsyncCalls(運(yùn)行中的異步請求)隊(duì)列和readyAsyncCalls(就緒狀態(tài)的異步請求)隊(duì)列捣染。
   使運(yùn)行中的異步請求不超過兩種最大值骄瓣,并且如果隊(duì)列有空閑,將就緒狀態(tài)的請求歸類為運(yùn)行中耍攘。
*/
private void promoteCalls() {
    //運(yùn)行中的異步請求隊(duì)列的請求數(shù)大于最大請求數(shù)榕栏,那么就沒必要去將就緒狀態(tài)的請求移動到運(yùn)行中。
    //其實(shí)就是說蕾各,如果有超過最大請求數(shù)的請求正在運(yùn)行扒磁,是不需要將其移出隊(duì)列的,繼續(xù)運(yùn)行完即可式曲。
    if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
    //如果就緒的隊(duì)列為空妨托,那就更沒有必要移動了,因?yàn)槎紱]有吝羞。
    if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

    //遍歷就緒隊(duì)列
    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {

      //取出一個請求
      AsyncCall call = i.next();
      //如果當(dāng)前請求對應(yīng)的host下兰伤,沒有超過maxRequestsPerHost,那么將其從就緒隊(duì)列中移除钧排,并加入在運(yùn)行隊(duì)列敦腔。
      if (runningCallsForHost(call) < maxRequestsPerHost) {
        //移除
        i.remove();
        //加入運(yùn)行隊(duì)列
        runningAsyncCalls.add(call);
        //立即執(zhí)行該請求
        executorService().execute(call);
      }

      //如果運(yùn)行隊(duì)列已經(jīng)到達(dá)了最大請求數(shù)上限,如果還有就緒中的請求恨溜,也不管了符衔。
      if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
    }
  }

獲取同個host下的請求數(shù)

/**
    很簡單的方法,對比已有的運(yùn)行中的請求和當(dāng)前請求的host糟袁,相同result++柏腻,返回即可
*/
private int runningCallsForHost(AsyncCall call) {
  int result = 0;
  for (AsyncCall c : runningAsyncCalls) {
    if (c.host().equals(call.host())) result++;
  }
  return result;
}

請求結(jié)束


/**
    同步請求結(jié)束
    當(dāng)該同步請求結(jié)束的時候,會調(diào)用此方法系吭,用于將運(yùn)行中的同步請求隊(duì)列中的該請求移除
    今后系列中五嫂,分析RealCall的時候,會知道何時調(diào)用此方法的。
 */
synchronized void finished(Call call) {
  if (!runningSyncCalls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
}

/**
    異步請求結(jié)束
    當(dāng)該異步請求結(jié)束的時候沃缘,會調(diào)用此方法躯枢,用于將運(yùn)行中的異步請求隊(duì)列中的該請求移除并調(diào)整請求隊(duì)列,此時就緒隊(duì)列中的請求可以

    今后系列中槐臀,分析AsyncCall的時候锄蹂,會知道何時調(diào)用此方法的。
 */
synchronized void finished(AsyncCall call) {
  if (!runningAsyncCalls.remove(call)) throw new AssertionError("AsyncCall wasn't running!");
  promoteCalls();
}

調(diào)整運(yùn)行隊(duì)列中的最大請求數(shù)量

/**
  以下兩個方法作用類似水慨,前者是調(diào)整總的最大請求數(shù)得糜,后者是調(diào)整單個host下的最大請求數(shù)
  調(diào)整之后會有兩種情況:
      1. 當(dāng)前隊(duì)列中的請求數(shù)大于最大請求數(shù):繼續(xù)執(zhí)行,將已在隊(duì)列中的請求執(zhí)行完成
      2. 當(dāng)前隊(duì)列中的請求數(shù)小于最大請求數(shù):如過就緒隊(duì)列中存在請求晰洒,將其移動到運(yùn)行隊(duì)列中朝抖,直到運(yùn)行隊(duì)列的大小大于等于對大請求數(shù)
*/
public synchronized void setMaxRequests(int maxRequests) {
  // 校驗(yàn)
  if (maxRequests < 1) {
    throw new IllegalArgumentException("max < 1: " + maxRequests);
  }
  // 設(shè)置
  this.maxRequests = maxRequests;
  // 調(diào)整請求
  promoteCalls();
}

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

剩余的方法

剩余的方法都是獲取一些數(shù)據(jù),大家自己看看就好谍珊。

總結(jié)


Dispatcher的作用為維護(hù)請求的狀態(tài)治宣,并維護(hù)一個線程池,用于執(zhí)行請求砌滞。

歡迎交流 QQ:2424334647

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末侮邀,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子贝润,更是在濱河造成了極大的恐慌绊茧,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件打掘,死亡現(xiàn)場離奇詭異华畏,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)胧卤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進(jìn)店門唯绍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拼岳,“玉大人枝誊,你說我怎么就攤上這事∠е剑” “怎么了叶撒?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長耐版。 經(jīng)常有香客問我祠够,道長,這世上最難降的妖魔是什么粪牲? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任古瓤,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘落君。我一直安慰自己穿香,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布绎速。 她就那樣靜靜地躺著皮获,像睡著了一般。 火紅的嫁衣襯著肌膚如雪纹冤。 梳的紋絲不亂的頭發(fā)上洒宝,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天,我揣著相機(jī)與錄音萌京,去河邊找鬼雁歌。 笑死,一個胖子當(dāng)著我的面吹牛枫夺,可吹牛的內(nèi)容都是我干的将宪。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼橡庞,長吁一口氣:“原來是場噩夢啊……” “哼较坛!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起扒最,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤丑勤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后吧趣,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體法竞,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年强挫,在試婚紗的時候發(fā)現(xiàn)自己被綠了岔霸。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡俯渤,死狀恐怖呆细,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情八匠,我是刑警寧澤侥钳,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布聋庵,位于F島的核電站威兜,受9級特大地震影響涨共,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜抡四,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一柜蜈、第九天 我趴在偏房一處隱蔽的房頂上張望仗谆。 院中可真熱鬧,春花似錦淑履、人聲如沸胸私。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽岁疼。三九已至,卻和暖如春缆娃,著一層夾襖步出監(jiān)牢的瞬間捷绒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工贯要, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留暖侨,地道東北人。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓崇渗,卻偏偏與公主長得像字逗,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子宅广,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內(nèi)容