在《OkHttp源码:连接池与响应缓存》一文中,我们大体了解了执行一次请求时,如何从连接池中获取一个可用连接。现在,我们再来看看连接池的更多细节。
选用连接
在3.0版本及之后,RealConnection
中有如下属性:
RealConnectionPool#transmitterAcquirePooledConnection
方法,从连接池中获取一个可用连接。
boolean transmitterAcquirePooledConnection(Address address, Transmitter transmitter, List<Route> routes, boolean requireMultiplexed) {
// 加锁
assert (Thread.holdsLock(this));
for (RealConnection connection : connections) {
if (requireMultiplexed && !connection.isMultiplexed()) continue;
// 连接是否可用
if (!connection.isEligible(address, routes)) continue;
// 将transmitter添加到RealConnectionPool.transmitters中
transmitter.acquireConnectionNoEvents(connection);
return true;
}
return false;
}
执行时每个请求时,都会为它创建一个Transmitter
实例,这个类是Request与Connection之间的中间层。
RealConnection#isEligible
中,首先判断与Connection
关联的transmitter
数是否大于1,如果是则说明该连接正在被某个请求使用中,不能被选中而并发使用。
关闭连接
当我们创建OkHttpClient
时,默认会创建一个maxIdleConnections=5、keepAliveDurationNs=5分钟的连接池。
当设置maxIdleConnections=0,或连接的noNewExchanges=true(被标记为空闲且不再接受请求),将被立即从连接池中移除。
关闭一个连接
RealConnectionPool有属性idleAtNanos,是该连接变为空闲时的时间戳,将用于计算该连接已空闲了多久。
那idleAtNanos何时被赋值呢?在Transmitter#releaseConnectionNoEvents
方法中,当RealConnection的List<Reference>变为empty时,令idleAtNanos=System.nanoTime()。
Socket releaseConnectionNoEvents() {
// 加锁
assert (Thread.holdsLock(connectionPool));
// 找出当前transmitter在connection.transmitters中的索引
int index = -1;
for (int i = 0, size = this.connection.transmitters.size(); i < size; i++) {
Reference<Transmitter> reference = this.connection.transmitters.get(i);
if (reference.get() == this) {
index = i;
break;
}
}
if (index == -1) throw new IllegalStateException();
// 移除当前transmitter
RealConnection released = this.connection;
released.transmitters.remove(index);
this.connection = null;
// 如果连接变得空闲,则记录idleAtNanos
if (released.transmitters.isEmpty()) {
// 设置为当前时间
released.idleAtNanos = System.nanoTime();
if (connectionPool.connectionBecameIdle(released)) {
return released.socket();
}
}
return null;
}
可见,RealConnection一直在ConnectionPool中,一次请求后,只是从transmitters
列表中移除本次请求的Transmitter,同时如果连接变的空闲,则记录idleAtNanos。
定时清理
在RealConnectionPool
中,有用于执行cleanup任务的线程池。虽然maximumPoolSize
相当于限制,但是某时刻最多允许一个线程在运行。
// 标记cleanup任务是否在运行中,防止并发。
boolean cleanupRunning;
private final Runnable cleanupRunnable = () -> {
while (true) {
// 返回到下次运行的等待时间
long waitNanos = cleanup(System.nanoTime());
if (waitNanos == -1) return;
if (waitNanos > 0) {
long waitMillis = waitNanos / 1000000L;
waitNanos -= (waitMillis * 1000000L);
synchronized (RealConnectionPool.this) {
try {
// wait,到期或被唤醒后,继续循环
RealConnectionPool.this.wait(waitMillis, (int) waitNanos);
} catch (InterruptedException ignored) {
}
}
}
}
};
cleanup逻辑如下:遍历连接池,找出空闲时间最长的那个连接a,以及空闲连接数idleConnectionCount;
- 首先,a连接的idleAtNanos > keepAliveDurationNs,或者idleConnectionCount > maxIdleConnections,移除该连接,返回0;
- 其次,空闲连接数没超过限制,且a连接空闲时间也未超限,说明没有可清理的连接,则返回a连接剩余存活时间;
- 其次,所有线程都在使用中,则返回keepAliveDurationNs;
- 最后,说明池中没有连接了,返回-1,cleanup线程将退出循环。
因此可知,执行一次cleanup
最多清理一个连接。且清理线程最多只有一个,不是无脑循环,有wait也有退出
立即关闭所有空闲连接
当你需要关闭OkHttpClient时,可以调用RealConnectionPool#evictAll
立即关闭空闲连接,而不受maxIdle、keepAliveDuration限制。
发现在spring-web包中OkHttp3ClientHttpRequestFactory#destroy
中,有调用evictAll
方法。