5.OkHttp請求調度的分析

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

連接池

OkHttp的鏈接遲相關的類是

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

整個流程如圖所示

鏈接池管理.png

任務隊列

說完連接池,,我們接著說他的任務隊列,這個任務隊列存在的場景是在調用enqueue里面的,相關的類是:

Dispatcher

沒圖我說個java8.。。

隊列管理.png

然后再根據圖看下面的流程

每個 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);
        }
    }
}

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

  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類負責分發了所有的請求,完成了所有請求的調度,避免了擁塞

現在上面的兩個流程已經分析完。 我們來畫張圖,把上面的流程串起來,整個也是OkHttp的請求調度的核心流程

image.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容