標籤:scm pmc mpm dom msm 包括 條件 ase plm
OkHttp3 有兩種運行方式:
1.同步阻塞調用並且直接返回;
2.通過內部線程池分發調度實現非阻塞的非同步回調;
下面講的是非阻塞非同步回調,OkHttp在多並髮網絡下的分發調度過程,主要是Dispatcher對象:
多線程:多線程技術主要解決處理器單元內多個線程執行的問題,它可以顯著減少處理器單元的閑置時間,增加處理器單元的吞吐能力。但如果對多線程應用不當,會增加對單個任務的處理時間
ThreadPool線程池:線程池的關鍵在於線程複用以減少非核心任務的損耗。
比如:
T = T1+T2+T3其中T1和T3是多線程本身的帶來的開銷(在Java中,通過映射pThead,並進一步通過SystemCall實現native線程),我們渴望減少T1,T3所用的時間,從而減少T的時間。但一些線程的使用者並沒有注意到這一點,所以在程式中頻繁的建立或銷毀線程,這導致T1和T3在T中佔有相當比例。顯然這是突出了線程的弱點(T1,T3),而不是優點(並發性)。
線程池技術正是關注如何縮短或調整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後相繼關閉所有線程。 反向 Proxy模型:
在OkHttp中,使用了與Nginx類似的反向 Proxy與分發技術,這是典型的單生產者多消費者問題。我們知道在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的非同步請求時,一般進行如下構造:當HttpClient的請求入隊時,根據代碼,我們可以發現實際上是Dispatcher進行了入隊操作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
OkHttp3 任務隊列