大工程搞完了,,咱們接著來摳細節,聊一聊OkHttp的連接池管理和任務隊列管理
連接池
OkHttp的鏈接遲相關的類是
- ConnectionPool
- StreamAllocation
如果這邊眼生的朋友請看之前的文章; StreamAllocation里面有個ConnectionPool的引用,SteamAllocation是協調connection,strams,calls 三者之間的關系的,我們按照之前的順序來看StreamAllocation具體和ConnectionPool之間有著什么不可描述的事情。
SteamAllocation在ConnectionInterceptor里面的調用的方法如下:
//獲取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 <StreamAllocation>
上述系列操作完之后就是一個RealConnection誕生了, 然連接上Socket,把當前的鏈接放到ConnectionPool里,調用的也是Internal.instance.put—>ConnectionPool.put>,把RealConnection傳遞過去,他的put操作:
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 == -1) return;
if (waitNanos > 0) {
long waitMillis = waitNanos / 1000000L;
waitNanos -= (waitMillis * 1000000L);// waitMillis = 300 000
synchronized (ConnectionPool.this) {
try {
ConnectionPool.this.wait(waitMillis, (int) waitNanos);
} catch (InterruptedException ignored) {
}
}
}
}
}
};
可以看出是不是要等待全靠cleanup (為啥我想到了4396.。。)
在此池上執行維護,如果超過了保持活動限制或空閑連接限制,則會逐漸消除空閑時間最長的連接 返回以納秒為單位的睡眠持續時間,直到下一次調用此方法。 如果不需要進一步的清理,則返回-1。
可以看到,okhttp的辣雞回收就在這個方法里,方法就在ConnectionPool里有興趣的同學可以去看一下,這里我直接說個比較關鍵的地方:
他會判斷鏈接是否在使用中,判斷的依據就是WeakReference是否為空(mmp,沒想到吧)如果為空的話就remove掉,把RealConnection的noNewStreams設置為true,這種方式依賴虛擬機的GC
整個流程如圖所示
任務隊列
說完連接池,,我們接著說他的任務隊列,這個任務隊列存在的場景是在調用enqueue里面的,相關的類是:
Dispatcher
沒圖我說個java8.。。
然后再根據圖看下面的流程
每個 OkHttpClient
只有一個任務隊列,是在OkHttpClient里面初始化的,在RealCall里面代用的時候會把這個AsyncCall添加到Dispatcher里面
@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的源碼
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的請求調度的核心流程