5.OkHttp請求排程的分析
大工程搞完了,,咱們接着來摳細節,聊一聊OkHttp的連接配接池管理和任務隊列管理
連接配接池
OkHttp的連結遲相關的類是
- ConnectionPool
- StreamAllocation
如果這邊眼生的朋友請看之前的文章; StreamAllocation裡面有個ConnectionPool的引用,SteamAllocation是協調connection,strams,calls 三者之間的關系的,我們按照之前的順序來看StreamAllocation具體和ConnectionPool之間有着什麼不可描述的事情。
SteamAllocation在ConnectionInterceptor裡面的調用的方法如下:
java
//擷取HttpCodec
HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
//開始連接配接
RealConnection connection = streamAllocation.connection();
然後對比到SteamAllocation裡面 ,他調用 findHealthyConnection —>findConnection—>Internal.instance.get(connectionPool, address, this, null);
Internal 是在okHttpClient裡面的靜态域裡面初始化的,他的get的具體就是調用connectionPool的get方法,我們直接找ConnectionPool.get(address, streamAllocation, route),這個方法是找在池裡面背回來的連結,如果沒有的話傳回null,再到下面進行初始化,直接new RealConnection(connectionPool, selectedRoute);建立了一個連接配接,然後添加計數,添加計數就是在RealConnection裡面的
public final List<Reference<StreamAllocation>> allocations = new ArrayList<>();
新增一個WeakReference \
上述系列操作完之後就是一個RealConnection誕生了, 然連接配接上Socket,把目前的連結放到ConnectionPool裡,調用的也是Internal.instance.put—>ConnectionPool.put>,把RealConnection傳遞過去,他的put操作:
“““java
void put(RealConnection connection) {
assert (Thread.holdsLock(this));
//如果清除空閑連結的線程沒啟動的話啟動清除空閑連結
if (!cleanupRunning) {
cleanupRunning = true;
executor.execute(cleanupRunnable);
}
//添加連結到connections
connections.add(connection);
}
“““
他的清理線程的工作就是一個while(true)的循環:
private final Runnable cleanupRunnable = new Runnable() {
@Override public void run() {
while (true) {
long waitNanos = cleanup(System.nanoTime());
if (waitNanos == -) return;
if (waitNanos > ) {
long waitMillis = waitNanos / L;
waitNanos -= (waitMillis * L);// waitMillis = 300 000
synchronized (ConnectionPool.this) {
try {
ConnectionPool.this.wait(waitMillis, (int) waitNanos);
} catch (InterruptedException ignored) {
}
}
}
}
}
};
可以看出是不是要等待全靠cleanup (為啥我想到了4396.。。)
java
在此池上執行維護,如果超過了保持活動限制或空閑連接配接限制,則會逐漸消除空閑時間最長的連接配接 傳回以納秒為機關的睡眠持續時間,直到下一次調用此方法。 如果不需要進一步的清理,則傳回-1。
可以看到,okhttp的辣雞回收就在這個方法裡,方法就在ConnectionPool裡有興趣的同學可以去看一下,這裡我直接說個比較關鍵的地方:
他會判斷連結是否在使用中,判斷的依據就是WeakReference是否為空(mmp,沒想到吧)如果為空的話就remove掉,把RealConnection的noNewStreams設定為true,這種方式依賴虛拟機的GC
整個流程如下圖所示
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0NXYFhGd192UvwVe0lmdhJ3ZvwFM38CXlZHbvN3cpR2Lc1TPB10QGtWUCpEMJ9CXsxWam9CXwADNvwVZ6l2c052bm9CXUJDT1wkNhVzLcRnbvZ2Lc1TPB1EMNR1T3VEVZZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39TM2UTN1czM4ETOyYDM3EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
任務隊列
說完連接配接池,,我們接着說他的任務隊列,這個任務隊列存在的場景是在調用enqueue裡面的,相關的類是:
Dispatcher
每個
OkHttpClient
隻有一個任務隊列,是在OkHttpClient裡面初始化的,在RealCall裡面代用的時候會把這個AsyncCall添加到Dispatcher裡面
java
@Override
public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
我們跑進去看看enqueue的源碼
java
public final class Dispatcher {
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
}
看這個方法 判斷的條件是:
- 目前運作的集合大小小宇最大請求數量,
- 目前運作的這個Host請求的數量小于最大Host的請求量
這邊都是利用Deque來實作的,之前的文章有講到過:
deque 即雙端隊列。是一種具有隊列和棧的性質的資料結構。雙端隊列中的元素可以從兩端彈出,其限定插入和删除操作在表的兩端進行。
如果符合條件,進去添加到runningAsyncCalls裡面,去執行,如果不符合條件就放到readyAsyncCalls裡面
在RealCall裡面的enqueue裡目前的執行完之後,在finally裡面就調用finished方法 ,會去readyAsyncCalls裡面尋找下一個要執行的AsnyCall
這裡的executorService()是一個線程池,可能關于線程池的一些東西大家不是特别清楚 這裡稍微解釋一下,首先是他的構造函數
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler)
corePoolSize: 線程池維護線程的最少數量
maximumPoolSize:線程池維護線程的最大數量
keepAliveTime: 線程池維護線程所允許的空閑時間
unit: 線程池維護線程所允許的空閑時間的機關
workQueue: 線程池所使用的緩沖隊列
handler: 線程池對拒絕任務的處理政策
上面的SynchronousQueue可能一般同學看的不是特别熟悉這裡解釋一下:
SynchronousQueue是一個沒有資料緩沖的BlockingQueue,生産者線程對其的插入操作put必須等待消費者的移除操作take,反過來也一樣。
SynchronousQueue的一個使用場景的典型就是線上程池裡。Executors.newCachedThreadPool()就使用了SynchronousQueue,這個線程池根據需要(新任務到來時)建立新的線程,如果有空閑線程則會重複使用,線程空閑了60秒後會被回收。執行是調用execute方法。
是以
整個dispatcher類負責分發了所有的請求,完成了所有請求的排程,避免了擁塞
我們用一張圖來總結
現在把上面流程整體串一下,也是OkHttp的核心