本文基于OkHttp3的3.11.0版本
implementation 'com.squareup.okhttp3:okhttp:3.11.0'
我們已經(jīng)分析了OkHttp3的攔截器鏈和緩存策略胸哥,今天我們再來看看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分鐘。下面我們來看看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;
}
ConnectionPool里的幾個(gè)重要變量:
(1)executor線程池,類似于CachedThreadPool闯团,用于執(zhí)行清理空閑連接的任務(wù)。
(2)Deque雙向隊(duì)列浪讳,同時(shí)具有隊(duì)列和棧的性質(zhì)涌萤,經(jīng)常在緩存中被使用,里面維護(hù)的RealConnection是socket物理連接的包裝
(3)RouteDatabase负溪,用來記錄連接失敗的路線名單
下面看看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);
}
}
從構(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;
}
}
緩存操作:添加楔脯、獲取、回收連接
(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方法找到符合條件的連接木柬,如果有符合條件的連接則復(fù)用。需要注意的是眉枕,這里還調(diào)用了streamAllocation的acquire方法。acquire方法的作用是對(duì)RealConnection引用的streamAllocation進(jìn)行計(jì)數(shù)寂玲,OkHttp3是通過RealConnection的StreamAllocation的引用計(jì)數(shù)是否為0來實(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;
}
}
//RealConnection.class
public final List<Reference<StreamAllocation>> allocations = new ArrayList<>();
每一個(gè)RealConnection中都有一個(gè)allocations變量,用于記錄對(duì)于StreamAllocation的引用伶授。StreamAllocation中包裝有HttpCodec流纹,而HttpCodec里面封裝有Request和Response讀寫Socket的抽象。每一個(gè)請求Request通過Http來請求數(shù)據(jù)時(shí)都需要通過StreamAllocation來獲取HttpCodec漱凝,從而讀取響應(yīng)結(jié)果,而每一個(gè)StreamAllocation都是和一個(gè)RealConnection綁定的愕乎,因?yàn)橹挥型ㄟ^RealConnection才能建立socket連接壁公。所以StreamAllocation可以說是RealConnection、HttpCodec和請求之間的橋梁紊册。
當(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();
}
(2)向緩存中添加連接
//ConnectionPool.class
void put(RealConnection connection) {
assert (Thread.holdsLock(this));
if (!cleanupRunning) {
cleanupRunning = true;
executor.execute(cleanupRunnable);
}
connections.add(connection);
}
添加連接之前會(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) {
}
}
}
}
}
};
cleanupRunnable中執(zhí)行清理任務(wù)是通過cleanup方法來完成,cleanup方法會(huì)返回下次需要清理的間隔時(shí)間倚聚,然后會(huì)調(diào)用wait方法釋放鎖和時(shí)間片凿可。等時(shí)間到了就再次進(jìn)行清理。下面看看具體的清理邏輯:
long cleanup(long now) {
//記錄活躍的連接數(shù)
int inUseConnectionCount = 0;
//記錄空閑的連接數(shù)
int idleConnectionCount = 0;
//空閑時(shí)間最長的連接
RealConnection longestIdleConnection = null;
long longestIdleDurationNs = Long.MIN_VALUE;
synchronized (this) {
for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
RealConnection connection = i.next();
//判斷連接是否在使用惨驶,也就是通過StreamAllocation的引用計(jì)數(shù)來判斷
//返回值大于0說明正在被使用
if (pruneAndGetAllocationCount(connection, now) > 0) {
//活躍的連接數(shù)+1
inUseConnectionCount++;
continue;
}
//說明是空閑連接敛助,所以空閑連接數(shù)+1
idleConnectionCount++;
//找出了空閑時(shí)間最長的連接,準(zhǔn)備移除
long idleDurationNs = now - connection.idleAtNanos;
if (idleDurationNs > longestIdleDurationNs) {
longestIdleDurationNs = idleDurationNs;
longestIdleConnection = connection;
}
}
if (longestIdleDurationNs >= this.keepAliveDurationNs
|| idleConnectionCount > this.maxIdleConnections) {
//如果空閑時(shí)間最長的連接的空閑時(shí)間超過了5分鐘
//或是空閑的連接數(shù)超過了限制续扔,就移除
connections.remove(longestIdleConnection);
} else if (idleConnectionCount > 0) {
//如果存在空閑連接但是還沒有超過5分鐘
//就返回剩下的時(shí)間焕数,便于下次進(jìn)行清理
return keepAliveDurationNs - longestIdleDurationNs;
} else if (inUseConnectionCount > 0) {
//如果沒有空閑的連接,那就等5分鐘后再嘗試清理
return keepAliveDurationNs;
} else {
//當(dāng)前沒有任何連接识脆,就返回-1善已,跳出循環(huán)
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);
//如果存在引用,就說明是活躍連接雕拼,就繼續(xù)看下一個(gè)StreamAllocation
if (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);
//如果沒有引用偎球,就移除
references.remove(i);
connection.noNewStreams = true;
//如果列表為空,就說明此連接上沒有StreamAllocation引用了袍冷,就返回0猫牡,表示是空閑的連接
if (references.isEmpty()) {
connection.idleAtNanos = now - keepAliveDurationNs;
return 0;
}
}
//遍歷結(jié)束后,返回引用的數(shù)量淌友,說明當(dāng)前連接是活躍連接
return references.size();
}
至此我們就分析完OkHttp3的連接池復(fù)用了震庭。
總結(jié)
(1)OkHttp3中支持5個(gè)并發(fā)socket連接瑰抵,默認(rèn)的keepAlive時(shí)間為5分鐘器联,當(dāng)然我們可以在構(gòu)建OkHttpClient時(shí)設(shè)置不同的值。
(2)OkHttp3通過Deque<RealConnection>來存儲(chǔ)連接肴颊,通過put渣磷、get等操作來管理連接。
(3)OkHttp3通過每個(gè)連接的引用計(jì)數(shù)對(duì)象StreamAllocation的計(jì)數(shù)來回收空閑的連接祟身,向連接池添加新的連接時(shí)會(huì)觸發(fā)執(zhí)行清理空閑連接的任務(wù)物独。清理空閑連接的任務(wù)通過線程池來執(zhí)行氯葬。