OkHttp 拆轮子之连接池

2026-06-01

本文基于 OkHttp 3.8.1,当前 OkHttp 已更新至 5.x,ConnectionPool 实现已重构,源码细节已过时。

OkHttp 内部还维护了一个连接池,用于缓存一定数量的连接,以减少与服务器建立连接时的资源开销。同时,为了保证缓存的连接数在一个合理的水平,连接池有一个最多闲置连接数量和最长连接闲置时长。这里我们还是通过分析一些关键方法来分析它的连接池机制:

ConnectionPool#get()

这个方法用于从连接池中获取一个可用的连接。

RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
    for (RealConnection connection : connections) {
        if (connection.isEligible(address, route)) {
            streamAllocation.acquire(connection);
            return connection;
        }
    }
    return null;
}

这里的实现也很简单,遍历连接池,找到可用的就返回,没找到就返回null。

ConnectionPool#put()

每次新建一个连接,都会往这个连接池里面丢。

void put(RealConnection connection) {
    if (!cleanupRunning) {
        cleanupRunning = true;
        executor.execute(cleanupRunnable);
    }
    connections.add(connection);
}

这里首先会判断有没有正在清理 多余 或者 闲置过久 的连接,没有的话,就先清理一波,然后把连接丢进去。

连接池的维护

连接池内部有一个专门负责清理冗余连接的线程。

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) {
                    }
                }
            }
        }
    }
};
 
long cleanup(long now) {
    int inUseConnectionCount = 0;
    int idleConnectionCount = 0;
    RealConnection longestIdleConnection = null;
    long longestIdleDurationNs = Long.MIN_VALUE;
//    下面的目的是找出一个需要关闭的连接或计算最短多久会有下一个连接过期
    synchronized (this) {
        for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
            RealConnection connection = i.next();
//    判断这个连接是否正在被使用,是就执行下一次循环
            if (pruneAndGetAllocationCount(connection, now) > 0) continue;
            idleConnectionCount++;
            // 找到最先过期的连接
            long idleDurationNs = now - connection.idleAtNanos;
            if (idleDurationNs > longestIdleDurationNs) {
                longestIdleDurationNs = idleDurationNs;
                longestIdleConnection = connection;
            }
        }
//    下面一串的判断就是判断目标连接现在是否已经过期,过期就关闭,否则返回距离过期的时间差
        if (longestIdleDurationNs >= this.keepAliveDurationNs
                || idleConnectionCount > this.maxIdleConnections) {
            connections.remove(longestIdleConnection);
        } else if (idleConnectionCount > 0) {
            return keepAliveDurationNs - longestIdleDurationNs;
        } else if (inUseConnectionCount > 0) {
            return keepAliveDurationNs;
        } else {
            cleanupRunning = false;
            return -1;
        }
    }
 
    closeQuietly(longestIdleConnection.socket());
    return 0;
}

大概的意思就是这个清理线程一直运行,它会不断的检查是否有过期的连接并进行关闭,然后暂停特定时间来进行下一次清理。

其他

OkHttp 内部有一个很重要的类— StreamAllocation ,这个类协调了 ConnectionStreamsCalls 之间的关系,作为一次请求( Call )的代表,在一个或多个连接( Connection )上传输一个或多个数据流( Stream )。 整个请求流程中,这个类最先出现在 RetryAndFollowUpInterceptor 里面,当简单了解这个类的内部实现之后,对请求的逻辑也能更好的理解。整体请求流程可参考 2017-12-30-OkHttp-拆轮子之请求流程简析