OkHttp源碼解讀總結(五)--->OkHttp核心調度器Dispatcher類源碼總結

OkHttp源碼解讀總結(五)—>OkHttp核心調度器Dispatcher類源碼總結

標籤(空格分隔): OkHttp源碼 學習筆記


前言

  • 以下的相關知識總結是通過慕課網的相關學習和自己的相關看法,如果有需要的可以去查看一下慕課網的相關教學,感覺還可以。

okhttp是如何實現同步和異步的請求呢?

Dispatcher

Dispatcher的作用

  • 發送的同步/異步請求都會在Dispatcher中管理其狀態

到底什麼是Dispatcher?

  • Dispatcher的作用是維護請求的狀態
  • 並維護一個線程池,用於執行請求。

Dispatcher源碼

成員變量

 //最大同時請求數
 private int maxRequests = 64;
 //同時最大的相同Host的請求數
 private int maxRequestsPerHost = 5;
 private @Nullable Runnable idleCallback;
 //線程池  維護執行請求和等待請求  
 private @Nullable ExecutorService executorService;
 //異步的等待隊列
 private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
 //異步的執行對列
 private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
 //同步的執行隊列
 private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

異步請求爲什麼需要兩個隊列?

我們可以知道,對於異步的請求隊列,有一個是異步的執行隊列和一個異步的等待隊列。
這個可以理解爲生產者和消費者模型

  • Dispatcher–>生產者(默認主線程)
  • ExecutorService–>消費者
    • readyAsyncCalls–>異步緩存隊列
    • runningAsyncCalls–>異步執行隊列

因此對於下方的這個流程圖就比較容易理解了,當客戶端通過call.enqueue(runnable)方法之後,這個是Dispatcher就會進行分發,當判斷當前的最大請求數是否還在允許範圍內&最大主機也是允許範圍內,那麼就會把這個請求扔到異步執行隊列中,然後開闢新的線程進行網絡請求,當不符合上述規則的時候,就把這個請求扔到異步等待隊列當中,直到之前的異步執行隊列中有空餘的線程就也就是調用promoteCall()刪除執行隊列,就會從異步等待隊列中獲取需要執行的網絡請求。

image.png

對於同步的請求,當調用execute()方法之後

 synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }

這個方法也很簡單,就是執行把這個Call請求添加到同步執行隊列當中。

對於異步的請求,當調用enqueue()方法之後,最終執行的代碼

 synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      //噹噹前的AsyncCall可以立即執行
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      //不能立即執行  需要等待
      readyAsyncCalls.add(call);
    }
  }

ExecutorService

 public synchronized ExecutorService executorService() {
    if (executorService == null) {
      //這個在創建線程池的時候,設定核心線程池個數爲0  最大的線程數(但是由於有其他限制,這個也不是無線的創建線程) 非核心線程的KeepLive空閒時間爲60s   任務隊列爲SynchronousQueue 也就是當沒有請求的時候 過60s  就會清空這個線程池
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
  }

這個主要就是調度正在執行的隊列等待執行隊列,當正在執行隊列有完成的,就會把等待隊列中優先級高的調整添加到正在執行的隊列當中,同時把他從等待執行隊列中移除。保證網絡請求的高效運轉。

Call執行完肯定需要在runningAsyncCalls隊列中移除這個線程

  • 那麼readyAsyncCalls隊列中的線程在什麼時候纔會被執行呢?
    因爲異步的請求,需要我們把之前封裝好的AsyncCall(runnable),這個入隊。當然我們可以查看這個AsyncCall的execute()方法,按理說應該是run()方法,但是在他的上層(NamedRunnable)裏面對run()方法進行了相關邏輯,出來了一個execute()方法,我們可以看到AsyncCall的execute()方法執行的finally語句最終會執行一句 client.dispatcher().finished(this);,如果跟進去看源碼的時候,最終會調用下面的代碼塊
 finished(runningAsyncCalls, call, true);

   //首先把當前的請求從正在執行的請求隊列中刪除
   if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");

//也就是promoteCalls始終爲true(當是異步請求的時候)
 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實例
      AsyncCall call = i.next();
      //判斷所有運行的主機是否小於最大的限制
      if (runningCallsForHost(call) < maxRequestsPerHost) {
        //如果都符合  那麼就把這個AsyncCall從等待隊列中刪除
        i.remove();
        //把這個AsyncCall(等待的)添加到正在執行的請求隊列中
        runningAsyncCalls.add(call);
        //通過線城市ExecutorService執行請求
        executorService().execute(call);
      }

      if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
    }
  }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章