OkHttp3 有兩種運行方式:
1.同步阻塞調用並且直接返回;
2.通過內部線程池分發調度實現非阻塞的異步回調;
下面講的是非阻塞異步回調,OkHttp在多並發網絡下的分發調度過程,主要是Dispatcher對象:
多線程:多線程技術主要解決處理器單元內多個線程執行的問題,它可以顯著減少處理器單元的閑置時間,增加處理器單元的吞吐能力。但如果對多線程應用不當,會增加對單個任務的處理時間
ThreadPool線程池:線程池的關鍵在於線程復用以減少非核心任務的損耗。
比如:
T = T1+T2+T3其中T1和T3是多線程本身的帶來的開銷(在Java中,通過映射pThead,並進一步通過SystemCall實現native線程),我們渴望減少T1,T3所用的時間,從而減少T的時間。但一些線程的使用者並沒有注意到這一點,所以在程序中頻繁的創建或銷毀線程,這導致T1和T3在T中占有相當比例。顯然這是突出了線程的弱點(T1,T3),而不是優點(並發性)。
1.通過對線程進行緩存,減少了創建銷毀的時間損失;
2.通過控制線程數量閥值,減少了當線程過少時帶來的CPU閑置(比如說長時間卡在I/O上了)與線程過多時對JVM的內存與線程切換時系統調用的壓力.
構造單例線程池:
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;
}
參數說明:
- int corePoolSize: 最小並發線程數,這里並發同時包括空閑與活動的線程,如果是0的話,空閑一段時間后所有線程將全部被銷毀。
- int maximumPoolSize: 最大線程數,當任務進來時可以擴充的線程最大值,當大於了這個值就會根據丟棄處理機制來處理
- long keepAliveTime: 當線程數大於
corePoolSize時,多余的空閑線程的最大存活時間,類似於HTTP中的Keep-alive- TimeUnit unit: 時間單位,一般用秒
- BlockingQueue<Runnable> workQueue: 工作隊列,先進先出,可以看出並不像Picasso那樣設置優先隊列。(阻塞隊列)
- ThreadFactory threadFactory: 單個線程的工廠,可以打Log,設置
Daemon(即當JVM退出時,線程自動結束)等。可以看出,在Okhttp中,構建了一個閥值為[0, Integer.MAX_VALUE]的線程池,它不保留任何最小線程數,隨時創建更多的線程數,當線程空閑時只能活60秒,它使用了一個不存儲元素的阻塞工作隊列,一個叫做"OkHttp Dispatcher"的線程工廠。也就是說,在實際運行中,當收到10個並發請求時,線程池會創建十個線程,當工作完成后,線程池會在60s后相繼關閉所有線程。反向代理模型:
在OkHttp中,使用了與Nginx類似的反向代理與分發技術,這是典型的單生產者多消費者問題。我們知道在Nginx中,用戶通過HTTP(Socket)訪問前置的服務器,服務器會添加Header並自動轉發請求給后端集群,接着返回數據結果給用戶(比如簡書上次掛了也顯示了Nginx報錯)。通過將工作分配給多個后台服務器並共享Session,可以提高服務的負載均衡能力,實現非阻塞、高可用、高並發連接,避免資源全部放到一台服務器而帶來的負載,速度,在線率等影響。
而在OkHttp中,非常類似於上述場景,它使用 Dispatcher作為任務的派發器,線程池對應多台后置服務器,用AsyncCall對應Socket請求,用Deque<readyAsyncCalls>對應Nginx的內部緩存具體成員如下
- maxRequests = 64: 最大並發請求數為64
- maxRequestsPerHost = 5: 每個主機最大請求數為5
- Dispatcher: 分發者,也就是生產者(默認在主線程)
- AsyncCall: 隊列中需要處理的Runnable(包裝了異步回調接口)
- ExecutorService:消費者池(也就是線程池)
- Deque<readyAsyncCalls>:緩存(用數組實現,可自動擴容,無大小限制)
- Deque<runningAsyncCalls>:正在運行的任務,僅僅是用來引用正在運行的任務以判斷並發量,注意它並不是消費者緩存
通過將請求任務分發給多個線程,可以顯著的減少I/O等待時間
OkHttp的任務調度
當我們希望使用OkHttp的異步請求時,一般進行如下構造:![]()
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
//添加正在運行的請求
runningAsyncCalls.add(call);
//線程池執行請求
executorService().execute(call);
} else {
//添加到緩存隊列排隊等待
readyAsyncCalls.add(call);
}
}可以發現請求是否進入緩存的條件如下:(runningRequests<64 && runningRequestsPerHost<5)如果滿足條件,那么就直接把AsyncCall直接加到runningCalls的隊列中,並在線程池中執行(線程池會根據當前負載自動創建,銷毀,緩存相應的線程)。反之就放入readyAsyncCalls進行緩存等待。我們再分析請求元素AsyncCall(它實現了Runnable接口),它內部實現的execute方法如下:
當任務執行完成后,無論是否有異常,![]()
finally代碼段總會被執行,也就是會調用Dispatcher的finished函數,打開源碼,發現它將正在運行的任務Call從隊列runningAsyncCalls中移除后,接着執行promoteCalls()函數
這樣,就主動的把緩存隊列向前走了一步,而沒有使用互斥鎖等復雜編碼.地址:通過上述的分析,我們知道了:
- OkHttp采用Dispatcher技術,類似於Nginx,與線程池配合實現了高並發,低阻塞的運行
- Okhttp采用Deque作為緩存,按照入隊的順序先進先出
- OkHttp最出彩的地方就是在try/finally中調用了
finished函數,可以主動控制等待隊列的移動,而不是采用鎖或者wait/notify,極大減少了編碼復雜性http://www.jianshu.com/p/aad5aacd79bf
