深刻OKHttp源碼分析(二)----OkHttp任務調度核心類Dispatcher解析

OkHttp任務調度核心類Dispatcher解析

上一篇咱們分析了okhttp的同步和異步請求的執行流程並進行了源碼分析,深刻OKHttp源碼分析(一)----同步和異步請求流程和源碼分析 那麼今天咱們來看看在整個執行流程中起到關鍵做用的Dispatcher調度類。首先咱們來看看這個類中的幾個全局變量bash

/** 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<>(); 複製代碼

咱們先來看下面三個變量 readyAsyncCalls:等待執行的異步任務隊列,用來存放異步任務,若是線程池中的任務數量已經超過或已經達到指定的最大的任務數,那麼久將異步任務放入這個隊列中。 runningAsyncCalls:正在執行的異步任務隊列,若是線程池中的任務數量小於指定的最大任務數,就將異步任務添加到這個隊列中, runningSyncCalls:正在執行的同步任務隊列異步

接着回來咱們看executorService,這個是一個線程池,怎麼證實呢,咱們能夠看到下面這個方法async

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

咱們能夠看到,果真是new出來一個線程池並複製給了executorService,咱們來看下線程池的構造函數中的參數,來一一分析下: 第一個參數0,指的是這個線程池中的核心線程數量,若是線程池中沒有任務的話,那麼非核心線程在通過一段時間的等待後就會關閉,以節省資源。 第二個參數Integer.MAX_VALUE,這個參數指的是這個線程池中的最大線程數,設置成了integer的最大值,咱們知道,這個值是很是大的,若是真的建立了這麼多的線程,那麼咱們的手機不會掛掉嗎???其實不會的,由於okhttp在其餘地方已經進行了判斷,若是線程池中的線程數量達到了指定的數值,就不會將任務添加到線程池中,也就不會新建線程了。具體是在哪裏判斷的,咱們稍後介紹。 第三個參數60和第四個參數TimeUnit.SECONDS,這個指的就是空閒線程的存活時間,第三個參數指定數值,第四個參數指定單位。 第五個參數new SynchronousQueue():其實就是線程池中的線程的管理隊列 第六個參數Util.threadFactory("OkHttp Dispatcher", false):這個是線程的建立工廠,線程的建立就是經過第六個參數進行的,有興趣的朋友可點進去繼續看下,咱們就不貼出來啦。函數

咱們再來看下上一篇遇到過的一個方法源碼分析

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

咱們看下if語句的條件中的兩個變量maxRequests,maxRequestsPerHost,咱們看下定義,post

private int maxRequests = 64;
  private int maxRequestsPerHost = 5;
複製代碼

這兩個值是什麼意思呢,其實,maxRequests就是線程池中的最大任務數,當線程池中的任務數量達到這個值以後,就再也不往線程池中繼續添加任務,因此,即便在建立線程池的時候指定了線程池的最大任務數是integer的最大值,也是不可能達到的,這就是緣由。maxRequestsPerHost是每一個主機的最大請求數,當線程池中的任務對同一個主機的請求達到這個數值以後,就不能繼續往線程池中添加對這個主機的請求任務,不知道這樣說你們能不能理解???? 咱們回過頭來看下這條語句runningCallsForHost(call),點進去看下這個方法的實現ui

private int runningCallsForHost(AsyncCall call) {
    int result = 0;
    for (AsyncCall c : runningAsyncCalls) {
      if (c.get().forWebSocket) continue;
      if (c.host().equals(call.host())) result++;
    }
    return result;
  }
複製代碼

咱們能夠看到,裏面是對線程池中正在執行的任務進行遍歷操做,當要添加的任務的請求的主機和正在進行的任務的主機一致,result就加1,最後獲得對這個主機的請求數量,而後在前面的if語句中進行判斷是否小於指定的值,若是小於,就將異步任務添加到正在執行的異步任務隊列中,並添加到線程池中,不然就添加到等待的異步任務隊列中。this

咱們再來看一個前面分析過的方法,finished方法,這個方法在哪裏會被調用呢?若是你不清楚,請看前面一篇文章的分析,咱們看下這個方法的實現spa

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 == 0 && idleCallback != null) {
      idleCallback.run();
    }
  }
複製代碼

在加鎖的代碼塊中,首先將這個異步請求的任務從執行的隊列中移除,而後咱們看下這句線程

if (promoteCalls) 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.
    }
  }
複製代碼

首先判斷當前正在運行的隊列中的任務數量是否達到了指定的數值,若是等於或大於指定數值,就返回,接着判斷等待隊列是否爲空,若是爲空,說明沒有要添加的任務,也返回便可。 下面對等待的任務隊列進行遍歷,首先取出一個異步任務,判斷該任務請求的主機地址在正在執行的隊列中的數量是否達到了指定的最大任務數,若是沒有達到,就從等待隊列中移除,並添加到正在運行的隊列中,並加入線程池中執行。 最後判斷正在運行的任務隊列中的任務數量是否達到了指定的最大任務數,若是大於最大任務數,就跳出for循環,不然,就繼續從等待隊列中取出任務進行操做。 這個方法分析完了,咱們回到finished方法中,繼續往下看

runningCallsCount = runningCallsCount();
複製代碼

上一篇咱們介紹過,這個語句只是從新計算了正在執行的任務數量,包括同步和異步任務。 finished方法最後部分是判斷運行任務數量是否爲0,若是沒有了運行任務,就執行回收線程進行資源回收操做。 到此爲止呢,咱們就將okhttp的任務調度核心類Dispatcher的主要代碼又分析了一遍,相信你們能對這個類有了較爲深入的認識了,這個類的主要功能就是對同步和異步任務進行調度處理,至關於任務的指揮者,大概就像是十字路口的交警同志吧,讓你等你就等,讓你走你就得走,可能比喻不是很是恰當,只要你們都明白便可,下一篇咱們就對okhttp的攔截器進行介紹和分析。

相關文章
相關標籤/搜索