天天看點

5.OkHttp請求排程的分析連接配接池

5.OkHttp請求排程的分析

大工程搞完了,,咱們接着來摳細節,聊一聊OkHttp的連接配接池管理和任務隊列管理

連接配接池

OkHttp的連結遲相關的類是

  1. ConnectionPool
  2. 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

整個流程如下圖所示

5.OkHttp請求排程的分析連接配接池

任務隊列

說完連接配接池,,我們接着說他的任務隊列,這個任務隊列存在的場景是在調用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); } } }

看這個方法 判斷的條件是:

  1. 目前運作的集合大小小宇最大請求數量,
  2. 目前運作的這個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類負責分發了所有的請求,完成了所有請求的排程,避免了擁塞

我們用一張圖來總結

5.OkHttp請求排程的分析連接配接池

現在把上面流程整體串一下,也是OkHttp的核心

5.OkHttp請求排程的分析連接配接池

繼續閱讀