前面一篇文章【OkHttp3 執(zhí)行流程】里面分析了OkHttp3的請(qǐng)求過(guò)程奠衔。今天的文章將對(duì)OkHttp3的連接池的復(fù)用進(jìn)行深一步的分析沉御,通過(guò)對(duì)連接池的管理窃爷,復(fù)用連接粤剧,減少了頻繁的網(wǎng)絡(luò)請(qǐng)求導(dǎo)致性能下降的問(wèn)題。我們知道长窄,Http是基于TCP協(xié)議的滔吠,而TCP建立連接需要經(jīng)過(guò)三次握手,斷開(kāi)需要經(jīng)過(guò)四次揮手挠日,因此疮绷,Http中添加了一種KeepAlive機(jī)制,當(dāng)數(shù)據(jù)傳輸完畢后仍然保持連接嚣潜,等待下一次請(qǐng)求時(shí)直接復(fù)用該連接冬骚。
1、找到獲取連接的入口郑原,ConnectInterceptor的intercept()方法
public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
// 1
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
在注釋1處利用StreamAllocation的newStream()獲取httpCodec對(duì)象唉韭,這個(gè)過(guò)程會(huì)從連接池中尋找是否有可用連接,若有犯犁,則返回;若沒(méi)有女器,則創(chuàng)建一個(gè)新的連接酸役,并加入連接池中。
2、newStream()的內(nèi)部實(shí)現(xiàn)
public HttpCodec newStream(
OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
int connectTimeout = chain.connectTimeoutMillis();
int readTimeout = chain.readTimeoutMillis();
int writeTimeout = chain.writeTimeoutMillis();
int pingIntervalMillis = client.pingIntervalMillis();
boolean connectionRetryEnabled = client.retryOnConnectionFailure();
try {
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
synchronized (connectionPool) {
codec = resultCodec;
return resultCodec;
}
} catch (IOException e) {
throw new RouteException(e);
}
}
在newStream()內(nèi)部調(diào)用findHealthyConnection()尋找可用連接涣澡。
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
boolean doExtensiveHealthChecks) throws IOException {
while (true) {
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
pingIntervalMillis, connectionRetryEnabled);
// If this is a brand new connection, we can skip the extensive health checks.
synchronized (connectionPool) {
if (candidate.successCount == 0) {
return candidate;
}
}
// Do a (potentially slow) check to confirm that the pooled connection is still good. If it
// isn't, take it out of the pool and start again.
if (!candidate.isHealthy(doExtensiveHealthChecks)) {
noNewStreams();
continue;
}
return candidate;
}
}
輪詢的方式尋找贱呐,利用findConnection()尋找一個(gè)候選連接,先判斷是否為一個(gè)全新的連接入桂,若是奄薇,跳過(guò)檢查,直接返回該連接抗愁;若不是馁蒂,則檢查該連接是否依然可用。
3蜘腌、findConnection()的內(nèi)部實(shí)現(xiàn)
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
boolean foundPooledConnection = false;
RealConnection result = null;
Route selectedRoute = null;
Connection releasedConnection;
Socket toClose;
synchronized (connectionPool) {
if (released) throw new IllegalStateException("released");
if (codec != null) throw new IllegalStateException("codec != null");
if (canceled) throw new IOException("Canceled");
//嘗試使用一個(gè)已分配的連接沫屡,但可能會(huì)限制我們創(chuàng)建新的流
releasedConnection = this.connection;
toClose = releaseIfNoNewStreams();
if (this.connection != null) {
// We had an already-allocated connection and it's good.
result = this.connection;
releasedConnection = null;
}
if (!reportedAcquired) {
// If the connection was never reported acquired, don't report it as released!
releasedConnection = null;
}
// 1. 試圖從連接池中獲取連接
if (result == null) {
Internal.instance.get(connectionPool, address, this, null);
if (connection != null) {
foundPooledConnection = true;
result = connection;
} else {
selectedRoute = route;
}
}
}
closeQuietly(toClose);
if (releasedConnection != null) {
eventListener.connectionReleased(call, releasedConnection);
}
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
}
if (result != null) {
// 如果上面從已分配或連接池其中一個(gè)能找到可用連接,則返回
return result;
}
... ...//省略代碼
//創(chuàng)建一個(gè)新的連接
result = new RealConnection(connectionPool, selectedRoute);
// 引用計(jì)數(shù)
acquire(result, false);
}
}
... ...//省略部分代碼
synchronized (connectionPool) {
reportedAcquired = true;
// 放入連接池
Internal.instance.put(connectionPool, result);
//如果另一個(gè)并發(fā)創(chuàng)建多路連接到相同的地址撮珠,則刪除重復(fù)數(shù)據(jù)
if (result.isMultiplexed()) {
socket = Internal.instance.deduplicate(connectionPool, address, this);
result = connection;
}
}
closeQuietly(socket);
eventListener.connectionAcquired(call, result);
return result;
}
上面代碼中有三個(gè)關(guān)鍵點(diǎn)沮脖,
1、Internal.instance.get() : 從連接池中獲取連接
2芯急、acquire(): 引用計(jì)數(shù)勺届,具體是對(duì)StreamAllocation的計(jì)數(shù),通過(guò)aquire()與release()操作RealConnection中的List<Reference<StreamAllocation>>列表娶耍。
3涮因、Internal.instance.put():將連接放入連接池中。
1和3中都有Internal.instance伺绽,instance實(shí)際就是Internal的一個(gè)實(shí)例养泡,在創(chuàng)建OkHttpClient時(shí)已對(duì)instance進(jìn)行了初始化,
4奈应、初始化Internal的instance
Internal.instance = new Internal() {
... ...//省略了部分代碼
@Override
public RealConnection get(ConnectionPool pool, Address address,
StreamAllocation streamAllocation, Route route) {
return pool.get(address, streamAllocation, route);
}
//刪除重復(fù)數(shù)據(jù)
@Override
public Socket deduplicate(
ConnectionPool pool, Address address, StreamAllocation streamAllocation) {
return pool.deduplicate(address, streamAllocation);
}
@Override
public void put(ConnectionPool pool, RealConnection connection) {
pool.put(connection);
}
};
}
通過(guò)上面代碼發(fā)現(xiàn)澜掩,從連接池獲取可用連接和添加新的連接到連接池,實(shí)際調(diào)用的是ConnectionPool的get()和put()方法杖挣。
5肩榕、連接池管理ConnectionPool
在分析get和put操作前,先看下ConnectionPool的一些關(guān)鍵屬性
//線程池惩妇,核心線程數(shù)為0株汉,最大線程數(shù)為最大整數(shù),線程空閑存活時(shí)間60s歌殃,
//SynchronousQueue 直接提交策略
private static final Executor executor = new ThreadPoolExecutor(0,
Integer.MAX_VALUE , 60L , TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));
//空閑連接的最大連接數(shù)
private final int maxIdleConnections;
//保持連接的周期
private final long keepAliveDurationNs;
//雙端隊(duì)列乔妈,存放具體的連接
private final Deque<RealConnection> connections = new ArrayDeque<>();
//用于記錄連接失敗的route
final RouteDatabase routeDatabase = new RouteDatabase();
//構(gòu)造函數(shù)
//從這里可以知道,空閑連接的最大連接數(shù)為5氓皱,保持連接的周期是5分鐘
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內(nèi)部的一些關(guān)鍵屬性后路召,首先看下ConnectionPool 的get()方法勃刨。
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;
}
在get()方法內(nèi)部對(duì)存放具體連接的雙端隊(duì)列connections進(jìn)行遍歷,如果連接有效股淡,則利用acquire()計(jì)數(shù)身隐。
//StreamAllocation # acquire()
public void acquire(RealConnection connection, boolean reportedAcquired) {
assert (Thread.holdsLock(connectionPool));
if (this.connection != null) throw new IllegalStateException();
this.connection = connection;
this.reportedAcquired = reportedAcquired;
//添加到RealConnection的allocations列表
connection.allocations.add(new StreamAllocationReference(this, callStackTrace));
}
//StreamAllocation # release()
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) {
//從RealConnection的allocations列表中移除
connection.allocations.remove(i);
return;
}
}
throw new IllegalStateException();
}
接著看ConnectionPool 的put()方法。
void put(RealConnection connection) {
assert (Thread.holdsLock(this));
if (!cleanupRunning) {
cleanupRunning = true;
executor.execute(cleanupRunnable);
}
connections.add(connection);
}
先判斷是否需要清理運(yùn)行中的連接唯灵,然后添加新的連接到連接池贾铝。接下來(lái)看看cleanupRunnable的實(shí)現(xiàn)。
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的run方法會(huì)不停的調(diào)用cleanup清理并返回下一次清理的時(shí)間間隔埠帕。然后進(jìn)入wait垢揩,等待下一次的清理。那么cleanup()是怎么計(jì)算時(shí)間間隔的搞监?
long cleanup(long now) {
int inUseConnectionCount = 0;
int idleConnectionCount = 0;
RealConnection longestIdleConnection = null;
long longestIdleDurationNs = Long.MIN_VALUE;
// Find either a connection to evict, or the time that the next eviction is due.
synchronized (this) {
//遍歷連接
for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
RealConnection connection = i.next();
//檢查連接是否是空閑狀態(tài)水孩,
//不是,則inUseConnectionCount + 1
//是 琐驴,則idleConnectionCount + 1
if (pruneAndGetAllocationCount(connection, now) > 0) {
inUseConnectionCount++;
continue;
}
idleConnectionCount++;
// If the connection is ready to be evicted, we're done.
long idleDurationNs = now - connection.idleAtNanos;
if (idleDurationNs > longestIdleDurationNs) {
longestIdleDurationNs = idleDurationNs;
longestIdleConnection = connection;
}
}
//如果超過(guò)keepAliveDurationNs或maxIdleConnections俘种,
//從雙端隊(duì)列connections中移除
if (longestIdleDurationNs >= this.keepAliveDurationNs
|| idleConnectionCount > this.maxIdleConnections) {
connections.remove(longestIdleConnection);
} else if (idleConnectionCount > 0) { //如果空閑連接次數(shù)>0,返回將要到期的時(shí)間
// A connection will be ready to evict soon.
return keepAliveDurationNs - longestIdleDurationNs;
} else if (inUseConnectionCount > 0) {
// 連接依然在使用中,返回保持連接的周期5分鐘
return keepAliveDurationNs;
} else {
// No connections, idle or in use.
cleanupRunning = false;
return -1;
}
}
closeQuietly(longestIdleConnection.socket());
// Cleanup again immediately.
return 0;
}
從上面可以知道绝淡,cleanupRunnable的主要工作是負(fù)責(zé)連接池的清理和回收宙刘。
總結(jié): OkHttp3連接池的復(fù)用主要是對(duì)雙端隊(duì)列Deque<RealConnection>進(jìn)行操作,通過(guò)對(duì)StreamAllocation的引用計(jì)數(shù)實(shí)現(xiàn)自動(dòng)回收牢酵。