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()刪除執行隊列,就會從異步等待隊列中獲取需要執行的網絡請求。
對於同步的請求,當調用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.
}
}