目錄介紹
- 01.線程池的理解
- 02.Dispatcher類詳解
- 03.OKHttp的任務調度
- 04.OKHttp調度優雅之處
01.線程池的理解
- android中的異步任務
- android的異步任務一般都是用Thread+Handler或者AsyncTask來實現,其中筆者當初經歷過各種各樣坑,特別是內存泄漏,當初筆者可是相當的欲死欲仙啊!所以現在很少有開發者還在用這一套來做異步任務,現在一般都是Rxjava爲主,當然還有自己自定義的異步任務框架(比如筆者),像RxJava都幫我們寫好了對應場景的線程池,這是爲什麼?
- 線程池的理解
- 對線程池的理解是有兩個層次,一種是狹隘的,一種是廣義的,那麼咱們各自都說下。
- 狹義上的線程池:線程池是一種多線程處理形式,處理過程中將任務添加到隊列中,後面再創建線程去處理這些任務,線程池裏面的線程都是後臺線程,每個線程都是默認的優先級下運。如果某個線程處於空閒中,將添加一個任務進來,讓空閒線程去處理任務。如果所有線程都很繁忙,消息隊列會掛起,等待某個線程池空閒後再處理任務。這樣可以保證線程數量不能超多最大數量。
- 廣義上的線程池:多線程技術主要是解決處理器單元內多個線程執行的問題,它可以顯著減少處理的單元閒置時間,增加處理器單元的吞吐能力。如果對多線程應用不當,會增加對單個任務的的處理時間。
- 舉一個例子
- 假如一個服務器完成一項任務的時間爲T:
- T1 創建線程的時間
- T2 在線程中執行任務的時間,包括線程同步所需要的時間
- T3 線程銷燬的時間
- 顯然 T= T1+T2+T3. 注意:這是一個理想化的情況
- 可以看出,T1,T3是多線程自身帶來的開銷(在Java中,通過映射pThread,並進一步通過SystemCall實現native線程),我們渴望減少T1和T3的時間,從而減少T的時間。但是一些線程的使用者並沒有注意到這一點,所以在線程中頻繁的創建或者銷燬線程,這導致T1和T3在T中佔有相當比例。這顯然突出的線程池的弱點(T1,T3),而不是有點(併發性)。
- 所以線程池的技術正是如何關注縮短或調整T1,T3時間的技術,從而提高服務器程序的性能。
- 1、通過對線程進行緩存,減少創建和銷燬時間的損失
- 2、通過控制線程數量的閥值,減少線程過少帶來的CPU閒置(比如長時間卡在I/O上了)與線程過多給JVM內存與線程切換時系統調用的壓力。
- 假如一個服務器完成一項任務的時間爲T:
02.Dispatcher類詳解
2.1 線程池executeService
- 首先看一下Dispatcher類這個代碼
/** Executes calls. Created lazily. */ private ExecutorService executorService; 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; }
- 由上面代碼可以得出Dispatcher內部實現了懶加載的無邊界限制的線程池。參數解析
- 1、0:核心線程數量,保持在線程池中的線程數量(即使已經空閒),爲0代表線程空閒後不會保留,等待一段時間後停止。
- 2、Integer.MAX_VALUE:表示線程池可以容納最大線程數量
- 3、TimeUnit.SECOND:當線程池中的線程數量大於核心線程時,空閒的線程就會等待60s纔會被終止,如果小於,則會立刻停止。
- 4、new SynchronousQueue():線程等待隊列。同步隊列,按序排隊,先來先服務
- 5、Util.threadFactory(“OkHttp Dispatcher”, false):線程工廠,直接創建一個名爲OkHttp Dispatcher的非守護線程。
- (1)SynchronousQueue每個插入操作必須等待另一個線程的移除操作,同樣任何一個移除操作都等待另一個線程的插入操作。因此隊列內部其實沒有任何一個元素,或者說容量爲0,嚴格說並不是一種容器,由於隊列沒有容量,因此不能調用peek等操作,因此只有移除元素纔有元素,顯然這是一種快速傳遞元素的方式,也就是說在這種情況下元素總是以最快的方式從插入者(生產者)傳遞給移除者(消費者),這在多任務隊列中最快的處理任務方式。對於高頻請求場景,無疑是最合適的。
- (2)在OKHttp中,創建了一個閥值是Integer.MAX_VALUE的線程池,它不保留任何最小線程,隨時創建更多的線程數,而且如果線程空閒後,只能多活60秒。所以也就說如果收到20個併發請求,線程池會創建20個線程,當完成後的60秒後會自動關閉所有20個線程。他這樣設計成不設上限的線程,以保證I/O任務中高阻塞低佔用的過程,不會長時間卡在阻塞上。
2.2 發起請求
- 整個框架主要通過Call來封裝每一次的請求。同時Call持有OkHttpClient和一份Request。而每一次的同步或者異步請求都會有Dispatcher的參與。
- (1)、同步
- Dispatcher在執行同步的Call:直接加入到runningSyncCall隊列中,實際上並沒有執行該Call,而是交給外部執行。
synchronized void executed(RealCall call) { runningSyncCalls.add(call); }
- (2)、異步
- 將Call加入隊列:如果當前正在執行的call的數量大於maxRequest(64),或者該call的Host上的call超過maxRequestsPerHos(5),則加入readyAsyncCall排隊等待,否則加入runningAsyncCalls並執行。
synchronized void enqueue(AsyncCall call) { if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { runningAsyncCalls.add(call); executorService().execute(call); } else { readyAsyncCalls.add(call); } }
2.3 結束請求
- 從ready到running,在每個call結束的時候都會調用finished。
- 可以知道finished先執行calls.remove(call)刪除call,然後執行promoteCalls(),在promoteCalls()方法裏面:如果當前線程大於maxRequest則不操作,如果小於maxRequest則遍歷readyAsyncCalls,取出一個call,並把這個call放入runningAsyncCalls,然後執行execute。在遍歷過程中如果runningAsyncCalls超過maxRequest則不再添加,否則一直添加。
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) { int runningCallsCount; Runnable idleCallback; synchronized (this) { if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!"); if (promoteCalls) promoteCalls(); runningCallsCount = runningCallsCount(); idleCallback = this.idleCallback; } if (runningCallsCount == 0 && idleCallback != null) { idleCallback.run(); } } 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 call = i.next(); if (runningCallsForHost(call) < maxRequestsPerHost) { i.remove(); runningAsyncCalls.add(call); executorService().execute(call); } if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity. } }
- promoteCalls()負責ready的Call到running的Call的轉化。
- 具體的執行請求則在RealCall裏面實現的,同步的在RealCall的execute裏面實現的,而異步的則在AsyncCall的execute裏面實現的。裏面都是調用RealCall的getResponseWithInterceptorChain的方法來實現責任鏈的調用。
03.OKHttp的任務調度
3.1 Dispatcher任務調度
- 在OKHttp中,它使用Dispatcher作爲任務的調度器。
- 在整個調度流程中涉及的成員如下:
- Dispatcher 對象是分發者,也是生產者(默認在主線程中)
- AsyncCall 對象其實是一個任務即Runnable(內部做了包裝異步接口)
- 類成員屬性大概有這些
// Dispatcher.java maxRequests = 64 // 最大併發請求數爲64 maxRequestsPerHost = 5 //每個主機最大請求數爲5 ExecutorService executorService //消費者池(也就是線程池) Deque<AsyncCall> readyAsyncCalls: // 異步的緩存,正在準備被消費的(用數組實現,可自動擴容,無大小限制) Deque<AsyncCall> runningAsyncCalls //正在運行的 異步的任務集合,僅僅是用來引用正在運行的任務以判斷併發量,注意它並不是消費者緩存 Deque<RealCall> runningSyncCalls //正在運行的,同步的任務集合。僅僅是用來引用正在運行的同步任務以判斷併發量
3.2 OKHttp同步調度流程分析
- 第一步是:是調用了RealCall的execute()方法裏面調用executed(this);
@Override public Response execute() throws IOException { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); try { client.dispatcher().executed(this); Response result = getResponseWithInterceptorChain(); if (result == null) throw new IOException("Canceled"); return result; } finally { client.dispatcher().finished(this); } }
- 第二步:在Dispatcher裏面的executed執行入隊操作
/** Used by {@code Call#execute} to signal it is in-flight. */ synchronized void executed(RealCall call) { runningSyncCalls.add(call); }
- 第三步:執行getResponseWithInterceptorChain();進入攔截器鏈流程,然後進行請求,獲取Response,並返回Response result 。
- 第四步:執行client.dispatcher().finished(this)操作
void finished(RealCall call) { finished(runningSyncCalls, call, false); }
3.3 OKHttp異步調度流程分析
- AsyncCall類簡介。在講解異步調度之前不得不提到AsyncCall這個類,AsyncCall,他其實是RealCall的內部類
//RealCall.java final class AsyncCall extends NamedRunnable { private final Callback responseCallback; AsyncCall(Callback responseCallback) { super("OkHttp %s", redactedUrl()); this.responseCallback = responseCallback; } String host() { return originalRequest.url().host(); } Request request() { return originalRequest; } RealCall get() { return RealCall.this; } @Override protected void execute() { boolean signalledCallback = false; try { //執行耗時任務 Response response = getResponseWithInterceptorChain(); if (retryAndFollowUpInterceptor.isCanceled()) { //retryAndFollowUpInterceptor取消了 執行失敗 signalledCallback = true; //回調,注意這裏回調是在線程池中,不是主線程 responseCallback.onFailure(RealCall.this, new IOException("Canceled")); } else { //一切正常走入正常流程 signalledCallback = true; responseCallback.onResponse(RealCall.this, response); } } catch (IOException e) { if (signalledCallback) { // Do not signal the callback twice! Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e); } else { responseCallback.onFailure(RealCall.this, e); } } finally { //最後執行出隊 client.dispatcher().finished(this); } } }
- 第一步 是調用了RealCall的enqueue()方法
- 在enqueue裏面調用了client.dispatcher().enqueue(new AsyncCall(responseCallback));方法
@Override public void enqueue(Callback responseCallback) { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); client.dispatcher().enqueue(new AsyncCall(responseCallback)); }
- 第二步:在Dispatcher裏面的enqueue執行入隊操作
synchronized void enqueue(AsyncCall call) { //判斷是否滿足入隊的條件(立即執行) if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { //正在運行的異步集合添加call runningAsyncCalls.add(call); //執行這個call executorService().execute(call); } else { //不滿足入隊(立即執行)條件,則添加到等待集合中 readyAsyncCalls.add(call); } }
- 上述代碼發現想要入隊需要滿足下面的條件:(runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost)
- 如果滿足條件,那麼就直接把AsyncCall直接加到runningCalls的隊列中,並在線程池中執行(線程池會根據當前負載自動創建,銷燬,緩存相應的線程)。反之就放入readyAsyncCalls進行緩存等待。
- runningAsyncCalls.size() < maxRequests 表示當前正在運行的AsyncCall是否小於maxRequests = 64
- runningCallsForHost(call) < maxRequestsPerHos 表示同一個地址訪問的AsyncCall是否小於maxRequestsPerHost = 5;即 當前正在併發的請求不能超過64且同一個地址的訪問不能超過5個
- 可以看到第二步中分爲兩種情況
- 可以直接入隊
- 不能直接入隊,需要等待
3.3.1 第一種情況
- 第三步:可以直接入隊
runningAsyncCalls.add(call);
- 第四步:線程池executorService執行execute()方法
- 由於AsyncCall繼承於NamedRunnable類,而NamedRunnable類又是Runnable類的實現類,所以走到了AsyncCall的execute()方法裏面。
executorService().execute(call);
- 第五步:執行AsyncCall的execute()方法
@Override protected void execute() { boolean signalledCallback = false; try { Response response = getResponseWithInterceptorChain(); if (retryAndFollowUpInterceptor.isCanceled()) { signalledCallback = true; responseCallback.onFailure(RealCall.this, new IOException("Canceled")); } else { signalledCallback = true; responseCallback.onResponse(RealCall.this, response); } } catch (IOException e) { if (signalledCallback) { // Do not signal the callback twice! Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e); } else { responseCallback.onFailure(RealCall.this, e); } } finally { client.dispatcher().finished(this); } }
- 第六步:執行getResponseWithInterceptorChain();進入攔截器鏈流程,然後進行請求,獲取Response。
- 第七步:如果是正常的獲取到Response,則執行responseCallback.onResponse()
- 第八步:執行client.dispatcher().finished(this)操作 進行出隊操作
- 注意這裏面第三個參數 同步是false,異步是true,如果是異步則需要進行是否添加繼續入隊的情景
void finished(AsyncCall call) { finished(runningAsyncCalls, call, true); }
3.3.2 第二種情況
- 第三步 不能直接入隊,需要等待
readyAsyncCalls.add(call);
- 第四步 觸發條件
- 能進入等待則說明當前要麼有64條正在進行的併發,要麼同一個地址有5個請求,所以要等待。
- 當有如下條件被滿足或者觸發的時候則執行promoteCalls操作
- 1 Dispatcher的setMaxRequestsPerHost()方法被調用時
public synchronized void setMaxRequestsPerHost(int maxRequestsPerHost) { //設置的maxRequestsPerHost不能小於1 if (maxRequestsPerHost < 1) { throw new IllegalArgumentException("max < 1: " + maxRequestsPerHost); } this.maxRequestsPerHost = maxRequestsPerHost; promoteCalls(); }
- 2 Dispatcher的setMaxRequests()被調用時
public synchronized void setMaxRequests(int maxRequests) { //設置的maxRequests不能小於1 if (maxRequests < 1) { throw new IllegalArgumentException("max < 1: " + maxRequests); } this.maxRequests = maxRequests; promoteCalls(); }
- 3當有一條請求結束了,執行了finish()的出隊操作,這時候會觸發promoteCalls()進行調整
if (promoteCalls) promoteCalls();
- 第五步 執行Dispatcher的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 call = i.next(); if (runningCallsForHost(call) < maxRequestsPerHost) { i.remove(); runningAsyncCalls.add(call); executorService().execute(call); } if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity. } }
- 第六步 先判斷是否滿足 初步入隊條件
- 如果此時 併發的數量還是大於maxRequests=64則return並繼續等待
- 如果此時,沒有等待的任務,則直接return並繼續等待
if (runningAsyncCalls.size() >= maxRequests) return; if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
- 第七步 滿足初步的入隊條件,進行遍歷,然後進行第二輪入隊判斷
- 進行同一個host是否已經有5請求在了,如果在了,則return返回並繼續等待。
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) { AsyncCall call = i.next(); if (runningCallsForHost(call) < maxRequestsPerHost) { i.remove(); runningAsyncCalls.add(call); executorService().execute(call); } if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity. }
- 第八步 此時已經全部滿足條件,則從等待隊列面移除這個call,然後添加到正在運行的隊列中
i.remove(); runningAsyncCalls.add(call);
- 第九步 線程池executorService執行execute()方法
executorService().execute(call);
- 第十步:執行AsyncCall的execute()方法
@Override protected void execute() { boolean signalledCallback = false; try { Response response = getResponseWithInterceptorChain(); if (retryAndFollowUpInterceptor.isCanceled()) { signalledCallback = true; responseCallback.onFailure(RealCall.this, new IOException("Canceled")); } else { signalledCallback = true; responseCallback.onResponse(RealCall.this, response); } } catch (IOException e) { if (signalledCallback) { // Do not signal the callback twice! Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e); } else { responseCallback.onFailure(RealCall.this, e); } } finally { client.dispatcher().finished(this); } }
- 第十一步:執行getResponseWithInterceptorChain();進入攔截器鏈流程,然後進行請求,獲取Response。
- 第十二步:如果是正常的獲取到Response,則執行responseCallback.onResponse()
- 第十三步:執行client.dispatcher().finished(this)操作 進行出隊操作
void finished(AsyncCall call) { finished(runningAsyncCalls, call, true); }
3.4 總結一下
- 1、異步流程總結,所以簡單的描述下異步調度爲:如果當前還能可以執行異步任務,則入隊,並立即執行,否則加入readyAsyncCalls隊列,當一個請求執行完畢後,會調用promoteCalls(),來把readyAsyncCalls隊列中的Async移出來並加入到runningAsyncCalls,並開始執行。然後在當前線程中去執行Call的getResponseWithInterceptorChain()方法,直接獲取當前的返回數據Response
- 2、對比同步和異步任務,我們會發現:同步請求和異步請求原理都是一樣的,都是在getResponseWithInterceptorChain()函數通過Interceptor鏈條來實現網絡請求邏輯,而異步任務則通過ExecutorService來實現的。PS:在Dispatcher中添加一個封裝了Callback的Call的匿名內部類AsyncCall來執行當前 的Call。這個AsyncCall是Call的匿名內部類。AsyncCall的execute方法仍然會回調到Call的 getResponseWithInterceptorChain方法來完成請求,同時將返回數據或者狀態通過Callback來完成。
04.OKHttp調度優雅之處
- 1、採用Dispacher作爲調度,與線程池配合實現了高併發,低阻塞的的運行
- 2、採用Deque作爲集合,按照入隊的順序先進先出
- 3、最精彩的就是在try/catch/finally中調用finished函數,可以主動控制隊列的移動。避免了使用鎖而wait/notify操作。