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

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

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

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

OKHttp3源碼解析系列

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

本文基于OkHttp3的3.11.0版本

implementation 'com.squareup.okhttp3:okhttp:3.11.0' 復(fù)制代碼

我們已經(jīng)分析了OkHttp3的攔截器鏈和緩存策略,今天我們?cè)賮?lái)看看OkHttp3的連接池復(fù)用。

客戶端和服務(wù)器建立socket連接需要經(jīng)歷TCP的三次握手和四次揮手,是一種比較消耗資源的動(dòng)作。Http中有一種keepAlive connections的機(jī)制,在和客戶端通信結(jié)束以后可以保持連接指定的時(shí)間。OkHttp3支持5個(gè)并發(fā)socket連接,默認(rèn)的keepAlive時(shí)間為5分鐘。下面我們來(lái)看看OkHttp3是怎么實(shí)現(xiàn)連接池復(fù)用的。

OkHttp3的連接池--ConnectionPool

public final class ConnectionPool {//線程池,用于執(zhí)行清理空閑連接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連接數(shù)private final int maxIdleConnections;//socket的keepAlive時(shí)間private final long keepAliveDurationNs;private final Deque<RealConnection> connections = new ArrayDeque<>();final RouteDatabase routeDatabase = new RouteDatabase();boolean cleanupRunning; } 復(fù)制代碼

ConnectionPool里的幾個(gè)重要變量:

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

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

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

下面看看ConnectionPool的構(gòu)造函數(shù)

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);} } 復(fù)制代碼

從構(gòu)造函數(shù)中可以看出,ConnectionPool的默認(rèn)空閑連接數(shù)為5個(gè),keepAlive時(shí)間為5分鐘。ConnectionPool是什么時(shí)候被創(chuàng)建的呢?是在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;} } 復(fù)制代碼

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

(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; } 復(fù)制代碼

獲取連接的邏輯比較簡(jiǎn)單,就遍歷連接池里的連接connections,然后用RealConnection的isEligible方法找到符合條件的連接,如果有符合條件的連接則復(fù)用。需要注意的是,這里還調(diào)用了streamAllocation的acquire方法。acquire方法的作用是對(duì)RealConnection引用的streamAllocation進(jìn)行計(jì)數(shù),OkHttp3是通過(guò)RealConnection的StreamAllocation的引用計(jì)數(shù)是否為0來(lái)實(shí)現(xiàn)自動(dòng)回收連接的。

//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;} } 復(fù)制代碼//RealConnection.class public final List<Reference<StreamAllocation>> allocations = new ArrayList<>(); 復(fù)制代碼

每一個(gè)RealConnection中都有一個(gè)allocations變量,用于記錄對(duì)于StreamAllocation的引用。StreamAllocation中包裝有HttpCodec,而HttpCodec里面封裝有Request和Response讀寫Socket的抽象。每一個(gè)請(qǐng)求Request通過(guò)Http來(lái)請(qǐng)求數(shù)據(jù)時(shí)都需要通過(guò)StreamAllocation來(lái)獲取HttpCodec,從而讀取響應(yīng)結(jié)果,而每一個(gè)StreamAllocation都是和一個(gè)RealConnection綁定的,因?yàn)橹挥型ㄟ^(guò)RealConnection才能建立socket連接。所以StreamAllocation可以說(shuō)是RealConnection、HttpCodec和請(qǐng)求之間的橋梁。

當(dāng)然同樣的StreamAllocation還有一個(gè)release方法,用于移除計(jì)數(shù),也就是將當(dāng)前的StreamAllocation的引用從對(duì)應(yīng)的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(); } 復(fù)制代碼

(2)向緩存中添加連接

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

添加連接之前會(huì)先調(diào)用線程池執(zhí)行清理空閑連接的任務(wù),也就是回收空閑的連接。

(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) {}}}}} }; 復(fù)制代碼

cleanupRunnable中執(zhí)行清理任務(wù)是通過(guò)cleanup方法來(lái)完成,cleanup方法會(huì)返回下次需要清理的間隔時(shí)間,然后會(huì)調(diào)用wait方法釋放鎖和時(shí)間片。等時(shí)間到了就再次進(jìn)行清理。下面看看具體的清理邏輯:

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

下面我們看看判斷連接是否是活躍連接的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);//如果存在引用,就說(shuō)明是活躍連接,就繼續(xù)看下一個(gè)StreamAllocationif (reference.get() != null) {i++;continue;}// We've discovered a leaked allocation. This is an application bug.//發(fā)現(xiàn)泄漏的引用,會(huì)打印日志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);//如果沒(méi)有引用,就移除references.remove(i);connection.noNewStreams = true;//如果列表為空,就說(shuō)明此連接上沒(méi)有StreamAllocation引用了,就返回0,表示是空閑的連接if (references.isEmpty()) {connection.idleAtNanos = now - keepAliveDurationNs;return 0;}}//遍歷結(jié)束后,返回引用的數(shù)量,說(shuō)明當(dāng)前連接是活躍連接return references.size(); } 復(fù)制代碼

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

總結(jié)

(1)OkHttp3中支持5個(gè)并發(fā)socket連接,默認(rèn)的keepAlive時(shí)間為5分鐘,當(dāng)然我們可以在構(gòu)建OkHttpClient時(shí)設(shè)置不同的值。

(2)OkHttp3通過(guò)Deque來(lái)存儲(chǔ)連接,通過(guò)put、get等操作來(lái)管理連接。

(3)OkHttp3通過(guò)每個(gè)連接的引用計(jì)數(shù)對(duì)象StreamAllocation的計(jì)數(shù)來(lái)回收空閑的連接,向連接池添加新的連接時(shí)會(huì)觸發(fā)執(zhí)行清理空閑連接的任務(wù)。清理空閑連接的任務(wù)通過(guò)線程池來(lái)執(zhí)行。


歡迎關(guān)注我的微信公眾號(hào),和我一起每天進(jìn)步一點(diǎn)點(diǎn)! 復(fù)制代碼

總結(jié)

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

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。