连接池
1.频繁的进行Socket连接和断开是非常消耗网络资源的,无限制的创建会导致性能低下,如果使用keep-live,连接就不会关闭将复用以前的connection,减少连接次数,提高效率。
2.复用连接就要对连接进行处理,就出现了连接池的概念。
3.OKhttp支持5个并发KeepAlive,默认链路生命为5分钟(链路空闲后,保持存活的时间),连接池有ConnectionPool实现,对连接进行回收和管理。
连接池使用
private final int maxIdleConnections;//每个地址最大可连接的数量
private final Deque<RealConnection> connections = new ArrayDeque<>();//双端队列,支持在头尾进行插入元素,用作后进先出(LIFO)堆栈,用于缓存数据
final RouteDatabase routeDatabase = new RouteDatabase();//用来记录连接失败的router
boolean cleanupRunning;//判断是否进行清理
双端队列
连接
public final class ConnectionPool {
void put(RealConnection connection) {
assert (Thread.holdsLock(this));
//没有任何连接时,cleanupRunning = false;
// 即没有任何链接时才会去执行executor.execute(cleanupRunnable);
// 从而保证每个连接池最多只能运行一个线程。
if (!cleanupRunning) {
cleanupRunning = true;
executor.execute(cleanupRunnable);
}
connections.add(connection);
}
}
总: 在向双端队列里存储数据的时候,会提前进行清理,清理闲置连接的线程。
获取链接
/** Returns a recycled connection to {@code address}, or null if no such connection exists. */
RealConnection get(Address address, StreamAllocation streamAllocation) {//物理地址,物理Socket被引用的次数
assert (Thread.holdsLock(this));
for (RealConnection connection : connections) {
if (connection.allocations.size() < connection.allocationLimit
&& address.equals(connection.route().address)
&& !connection.noNewStreams) {
streamAllocation.acquire(connection);//如果连接的次数小于限制的大小并且物理地址和缓存连接的物理地址一样,则获取列表中的connection作为连接
return connection;
}
}
return null;
}
移除连接
在将新的连接加入到双端队列之前,总会先执行cleanupRunnable方法
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) {
}
}
}
}
}
};
清理的实现主要在cleanup中,整体流程如下:
-
查询此链接内部的StreanAllocation的引用数量
-
标记空闲连接
-
如果空闲连接超过5个或者keepalive事件大于5分钟,则将该连接清理掉
-
返回此连接的到期时间,供下次进行清理
-
全部是活跃连接,则下次进行清理
-
没有任何连接,跳出连接
-
关闭连接,返回时间为0,立即进行下次清理
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();
// If the connection is in use, keep searching.
// 1. 判断是否是空闲连接
if (pruneAndGetAllocationCount(connection, now) > 0) {
inUseConnectionCount++;
continue;
}
idleConnectionCount++;
// If the connection is ready to be evicted, we're done.
// 2. 判断是否是最长空闲时间的连接
long idleDurationNs = now - connection.idleAtNanos;
if (idleDurationNs > longestIdleDurationNs) {
longestIdleDurationNs = idleDurationNs;
longestIdleConnection = connection;
}
}
// 3. 如果最长空闲的时间超过了设定的最大值,或者空闲链接数量超过了最大数量,则进行清理,否则计算下一次需要清理的等待时间
if (longestIdleDurationNs >= this.keepAliveDurationNs
|| idleConnectionCount > this.maxIdleConnections) {
// We've found a connection to evict. Remove it from the list, then close it below (outside
// of the synchronized block).
connections.remove(longestIdleConnection);
} else if (idleConnectionCount > 0) {
// A connection will be ready to evict soon.
return keepAliveDurationNs - longestIdleDurationNs;
} else if (inUseConnectionCount > 0) {
// All connections are in use. It'll be at least the keep alive duration 'til we run again.
return keepAliveDurationNs;
} else {
// No connections, idle or in use.
cleanupRunning = false;
return -1;
}
}
// 3. 关闭连接的socket
closeQuietly(longestIdleConnection.socket());
// Cleanup again immediately.
return 0;
}
通过pruneAndGetAllocationCount()判断是否是空闲连接,返回正在使用的StreamAllocation的引用数量
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);
//如果虚引用StreamAllocation正在被使用,则跳过进行下一次循环,
if (reference.get() != null) {
//引用计数
i++;
continue;
}
// We've discovered a leaked allocation. This is an application bug.
//如果 StreamAlloction 引用被回收,但是 connection 的引用列表中扔持有,那么可能发生了内存泄露
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);
//否则移除该StreamAllocation引用
references.remove(i);
connection.noNewStreams = true;
// If this was the last allocation, the connection is eligible for immediate eviction.
// 如果所有的StreamAllocation引用都没有了,返回引用计数0
if (references.isEmpty()) {
connection.idleAtNanos = now - keepAliveDurationNs;
return 0;
}
}
//返回引用列表的大小,作为引用计数
return references.size();
}
}
总结
okHttp连接池主要是使用一个双端队列来保存马上可以连接的连接,通过将需要建立的新连接与缓存列表中的连接进行比对,如果相同就进行复用,避免了重复连接,并且开启了一个线程来对闲置超时或者超出限制数量的连接进行清理。