日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

OkHttp3源码解析(三)——连接池复用

發布時間:2025/3/20 编程问答 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 OkHttp3源码解析(三)——连接池复用 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

OKHttp3源碼解析系列

  • OkHttp3源碼解析(一)之請求流程
  • OkHttp3源碼解析(二)——攔截器鏈和緩存策略

本文基于OkHttp3的3.11.0版本

implementation 'com.squareup.okhttp3:okhttp:3.11.0' 復制代碼

我們已經分析了OkHttp3的攔截器鏈和緩存策略,今天我們再來看看OkHttp3的連接池復用。

客戶端和服務器建立socket連接需要經歷TCP的三次握手和四次揮手,是一種比較消耗資源的動作。Http中有一種keepAlive connections的機制,在和客戶端通信結束以后可以保持連接指定的時間。OkHttp3支持5個并發socket連接,默認的keepAlive時間為5分鐘。下面我們來看看OkHttp3是怎么實現連接池復用的。

OkHttp3的連接池--ConnectionPool

public final class ConnectionPool {//線程池,用于執行清理空閑連接private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));//最大的空閑socket連接數private final int maxIdleConnections;//socket的keepAlive時間private final long keepAliveDurationNs;private final Deque<RealConnection> connections = new ArrayDeque<>();final RouteDatabase routeDatabase = new RouteDatabase();boolean cleanupRunning; } 復制代碼

ConnectionPool里的幾個重要變量:

(1)executor線程池,類似于CachedThreadPool,用于執行清理空閑連接的任務。

(2)Deque雙向隊列,同時具有隊列和棧的性質,經常在緩存中被使用,里面維護的RealConnection是socket物理連接的包裝

(3)RouteDatabase,用來記錄連接失敗的路線名單

下面看看ConnectionPool的構造函數

public ConnectionPool() {this(5, 5, TimeUnit.MINUTES); }public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {this.maxIdleConnections = maxIdleConnections;this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);// Put a floor on the keep alive duration, otherwise cleanup will spin loop.if (keepAliveDuration <= 0) {throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);} } 復制代碼

從構造函數中可以看出,ConnectionPool的默認空閑連接數為5個,keepAlive時間為5分鐘。ConnectionPool是什么時候被創建的呢?是在OkHttpClient的builder中:

public static final class Builder {...ConnectionPool connectionPool;...public Builder() {...connectionPool = new ConnectionPool();...}//我們也可以定制連接池public Builder connectionPool(ConnectionPool connectionPool) {if (connectionPool == null) throw new NullPointerException("connectionPool == null");this.connectionPool = connectionPool;return this;} } 復制代碼

緩存操作:添加、獲取、回收連接

(1)從緩存中獲取連接

//ConnectionPool.class @Nullable RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {assert (Thread.holdsLock(this));for (RealConnection connection : connections) {if (connection.isEligible(address, route)) {streamAllocation.acquire(connection, true);return connection;}}return null; } 復制代碼

獲取連接的邏輯比較簡單,就遍歷連接池里的連接connections,然后用RealConnection的isEligible方法找到符合條件的連接,如果有符合條件的連接則復用。需要注意的是,這里還調用了streamAllocation的acquire方法。acquire方法的作用是對RealConnection引用的streamAllocation進行計數,OkHttp3是通過RealConnection的StreamAllocation的引用計數是否為0來實現自動回收連接的。

//StreamAllocation.class public void acquire(RealConnection connection, boolean reportedAcquired) {assert (Thread.holdsLock(connectionPool));if (this.connection != null) throw new IllegalStateException();this.connection = connection;this.reportedAcquired = reportedAcquired;connection.allocations.add(new StreamAllocationReference(this, callStackTrace)); }public static final class StreamAllocationReference extends WeakReference<StreamAllocation> {public final Object callStackTrace;StreamAllocationReference(StreamAllocation referent, Object callStackTrace) {super(referent);this.callStackTrace = callStackTrace;} } 復制代碼//RealConnection.class public final List<Reference<StreamAllocation>> allocations = new ArrayList<>(); 復制代碼

每一個RealConnection中都有一個allocations變量,用于記錄對于StreamAllocation的引用。StreamAllocation中包裝有HttpCodec,而HttpCodec里面封裝有Request和Response讀寫Socket的抽象。每一個請求Request通過Http來請求數據時都需要通過StreamAllocation來獲取HttpCodec,從而讀取響應結果,而每一個StreamAllocation都是和一個RealConnection綁定的,因為只有通過RealConnection才能建立socket連接。所以StreamAllocation可以說是RealConnection、HttpCodec和請求之間的橋梁。

當然同樣的StreamAllocation還有一個release方法,用于移除計數,也就是將當前的StreamAllocation的引用從對應的RealConnection的引用列表中移除。

private void release(RealConnection connection) {for (int i = 0, size = connection.allocations.size(); i < size; i++) {Reference<StreamAllocation> reference = connection.allocations.get(i);if (reference.get() == this) {connection.allocations.remove(i);return;}}throw new IllegalStateException(); } 復制代碼

(2)向緩存中添加連接

//ConnectionPool.class void put(RealConnection connection) {assert (Thread.holdsLock(this));if (!cleanupRunning) {cleanupRunning = true;executor.execute(cleanupRunnable);}connections.add(connection);} 復制代碼

添加連接之前會先調用線程池執行清理空閑連接的任務,也就是回收空閑的連接。

(3)空閑連接的回收

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);synchronized (ConnectionPool.this) {try {ConnectionPool.this.wait(waitMillis, (int) waitNanos);} catch (InterruptedException ignored) {}}}}} }; 復制代碼

cleanupRunnable中執行清理任務是通過cleanup方法來完成,cleanup方法會返回下次需要清理的間隔時間,然后會調用wait方法釋放鎖和時間片。等時間到了就再次進行清理。下面看看具體的清理邏輯:

long cleanup(long now) {//記錄活躍的連接數int inUseConnectionCount = 0;//記錄空閑的連接數int idleConnectionCount = 0;//空閑時間最長的連接RealConnection longestIdleConnection = null;long longestIdleDurationNs = Long.MIN_VALUE;synchronized (this) {for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {RealConnection connection = i.next();//判斷連接是否在使用,也就是通過StreamAllocation的引用計數來判斷//返回值大于0說明正在被使用if (pruneAndGetAllocationCount(connection, now) > 0) {//活躍的連接數+1inUseConnectionCount++;continue;}//說明是空閑連接,所以空閑連接數+1idleConnectionCount++;//找出了空閑時間最長的連接,準備移除long idleDurationNs = now - connection.idleAtNanos;if (idleDurationNs > longestIdleDurationNs) {longestIdleDurationNs = idleDurationNs;longestIdleConnection = connection;}}if (longestIdleDurationNs >= this.keepAliveDurationNs|| idleConnectionCount > this.maxIdleConnections) {//如果空閑時間最長的連接的空閑時間超過了5分鐘//或是空閑的連接數超過了限制,就移除connections.remove(longestIdleConnection);} else if (idleConnectionCount > 0) {//如果存在空閑連接但是還沒有超過5分鐘//就返回剩下的時間,便于下次進行清理return keepAliveDurationNs - longestIdleDurationNs;} else if (inUseConnectionCount > 0) {//如果沒有空閑的連接,那就等5分鐘后再嘗試清理return keepAliveDurationNs;} else {//當前沒有任何連接,就返回-1,跳出循環cleanupRunning = false;return -1;}}closeQuietly(longestIdleConnection.socket());// Cleanup again immediately.return 0; } 復制代碼

下面我們看看判斷連接是否是活躍連接的pruneAndGetAllocationCount方法

private int pruneAndGetAllocationCount(RealConnection connection, long now) {List<Reference<StreamAllocation>> references = connection.allocations;for (int i = 0; i < references.size(); ) {Reference<StreamAllocation> reference = references.get(i);//如果存在引用,就說明是活躍連接,就繼續看下一個StreamAllocationif (reference.get() != null) {i++;continue;}// We've discovered a leaked allocation. This is an application bug.//發現泄漏的引用,會打印日志StreamAllocation.StreamAllocationReference streamAllocRef =(StreamAllocation.StreamAllocationReference) reference;String message = "A connection to " + connection.route().address().url()+ " was leaked. Did you forget to close a response body?";Platform.get().logCloseableLeak(message, streamAllocRef.callStackTrace);//如果沒有引用,就移除references.remove(i);connection.noNewStreams = true;//如果列表為空,就說明此連接上沒有StreamAllocation引用了,就返回0,表示是空閑的連接if (references.isEmpty()) {connection.idleAtNanos = now - keepAliveDurationNs;return 0;}}//遍歷結束后,返回引用的數量,說明當前連接是活躍連接return references.size(); } 復制代碼

至此我們就分析完OkHttp3的連接池復用了。

總結

(1)OkHttp3中支持5個并發socket連接,默認的keepAlive時間為5分鐘,當然我們可以在構建OkHttpClient時設置不同的值。

(2)OkHttp3通過Deque來存儲連接,通過put、get等操作來管理連接。

(3)OkHttp3通過每個連接的引用計數對象StreamAllocation的計數來回收空閑的連接,向連接池添加新的連接時會觸發執行清理空閑連接的任務。清理空閑連接的任務通過線程池來執行。


歡迎關注我的微信公眾號,和我一起每天進步一點點! 復制代碼

總結

以上是生活随笔為你收集整理的OkHttp3源码解析(三)——连接池复用的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。