OkHttp3 任務隊列


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后相繼關閉所有線程。
  反向代理模型:

  在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的異步請求時,一般進行如下構造:
當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()函數
這樣,就主動的把緩存隊列向前走了一步,而沒有使用互斥鎖等復雜編碼.

通過上述的分析,我們知道了:

  1. OkHttp采用Dispatcher技術,類似於Nginx,與線程池配合實現了高並發,低阻塞的運行
  2. Okhttp采用Deque作為緩存,按照入隊的順序先進先出
  3. OkHttp最出彩的地方就是在try/finally中調用了finished函數,可以主動控制等待隊列的移動,而不是采用鎖或者wait/notify,極大減少了編碼復雜性
地址:
http://www.jianshu.com/p/aad5aacd79bf

 






 


 

 

 

 

 

 

 

 

 



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM