yqqqq、 2026-01-30 20:16 采纳率: 0%
浏览 16

java多线程发送请求,报错http无可用连接

在超图下载瓦片时,报错org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool。
任务池有1W多瓦片需要下载,连接池我设置的是500最大连接数。代码里也对资源释放了如下图。为什么还是报无连接数可用,需要怎么修改?

img

img

img

img

  • 写回答

5条回答 默认 最新

  • 檀越@新空间 2026-01-30 20:17
    关注

    晚上好🌙🌙🌙
    本答案参考通义千问

    在使用 Java 多线程下载大量瓦片(如超图的瓦片服务)时,遇到 org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool 错误,说明连接池中没有可用的连接,即使你设置了最大连接数为 500。这通常意味着:

    • 连接池资源被耗尽
    • 未正确释放连接
    • 线程数量远大于连接池容量
    • 网络或服务器端限制

    ✅ 问题分析

    1. 连接池配置不足

    你设置的最大连接数是 500,但任务池中有 1W 多个瓦片需要下载。如果每个线程都试图获取一个连接,而连接池只能提供 500 个连接,那么超过这个数量的线程会阻塞等待,最终导致超时。

    重点:线程数 > 连接池最大连接数 = 会导致连接池耗尽。

    2. 资源未正确释放

    即使代码中对资源进行了释放,也可能因为以下原因导致连接未被回收:

    • 没有关闭 CloseableHttpResponse
    • 没有正确关闭 HttpClient
    • 使用了不正确的连接管理方式(如 PoolingHttpClientConnectionManager 没有正确配置)

    3. 并发线程数过高

    如果你使用的是 ExecutorService 创建了大量线程(例如 1000+),而连接池只有 500 个连接,就会出现“线程多于连接”的情况。


    ✅ 解决方案

    1. 控制并发线程数与连接池匹配

    确保线程池大小不超过连接池最大连接数,或者采用 线程池 + 信号量(Semaphore) 来控制并发数。

    示例代码(使用 Semaphore 控制并发):

    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Semaphore;
    
    public class TileDownloader {
    
        private static final int MAX_CONNECTIONS = 500;
        private static final Semaphore semaphore = new Semaphore(MAX_CONNECTIONS);
    
        public static void main(String[] args) {
            ExecutorService executor = Executors.newFixedThreadPool(1000); // 线程池可以更大,由信号量控制
    
            for (int i = 0; i < 10000; i++) {
                executor.submit(() -> {
                    try {
                        semaphore.acquire(); // 获取许可,最多同时执行 MAX_CONNECTIONS 个
                        downloadTile(); // 下载瓦片逻辑
                    } finally {
                        semaphore.release(); // 释放许可
                    }
                });
            }
    
            executor.shutdown();
        }
    
        private static void downloadTile() {
            // 实际的 HTTP 请求逻辑
            // 确保关闭 HttpClient 和响应对象
        }
    }
    

    重点:通过 Semaphore 控制实际并发数,避免超过连接池容量。


    2. 正确释放连接资源

    确保每次请求后都正确关闭 CloseableHttpResponseHttpClient,并使用 try-with-resources。

    示例代码(使用 try-with-resources):

    import org.apache.http.client.methods.CloseableHttpResponse;
    import org.apache.http.client.methods.HttpGet;
    import org.apache.http.impl.client.CloseableHttpClient;
    import org.apache.http.impl.client.HttpClients;
    import org.apache.http.util.EntityUtils;
    
    public class TileDownloader {
    
        public static void downloadTile(String url) {
            try (CloseableHttpClient httpClient = HttpClients.createDefault();
                 CloseableHttpResponse response = httpClient.execute(new HttpGet(url))) {
                if (response.getStatusLine().getStatusCode() == 200) {
                    String content = EntityUtils.toString(response.getEntity());
                    // 处理返回内容
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

    重点:使用 try-with-resources 确保资源自动关闭,防止连接泄漏。


    3. 配置连接池参数

    确保你的 PoolingHttpClientConnectionManager 配置合理,包括最大连接数、超时时间等。

    示例代码(配置连接池):

    import org.apache.http.client.config.RequestConfig;
    import org.apache.http.client.methods.HttpGet;
    import org.apache.http.impl.client.CloseableHttpClient;
    import org.apache.http.impl.client.HttpClients;
    import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
    
    public class HttpClientUtil {
    
        private static final int MAX_TOTAL_CONNECTIONS = 500;
        private static final int DEFAULT_PER_ROUTE = 200;
    
        public static CloseableHttpClient createClient() {
            PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager();
            connManager.setMaxTotal(MAX_TOTAL_CONNECTIONS);
            connManager.setDefaultMaxPerRoute(DEFAULT_PER_ROUTE);
    
            RequestConfig requestConfig = RequestConfig.custom()
                    .setConnectTimeout(5000)
                    .setSocketTimeout(5000)
                    .setConnectionRequestTimeout(5000)
                    .build();
    
            return HttpClients.custom()
                    .setConnectionManager(connManager)
                    .setRequestConfig(requestConfig)
                    .build();
        }
    }
    

    重点:配置合理的连接池参数,避免连接泄漏和超时。


    4. 增加连接池容量(如有必要)

    如果你确实需要更高的并发能力,可以适当增加连接池的 maxTotaldefaultMaxPerRoute,但注意不要超出系统或服务器的承受范围。

    connManager.setMaxTotal(1000);     // 最大连接数
    connManager.setDefaultMaxPerRoute(500); // 每个路由最大连接数
    

    重点:根据实际硬件和网络环境调整连接池大小。


    ✅ 总结

    | 问题 | 解决方案 | |------|----------| | 连接池资源耗尽 | 控制线程数,使用 Semaphore 或限制线程池大小 | | 资源未正确释放 | 使用 try-with-resources 确保关闭 HttpClientHttpResponse | | 连接池配置不合理 | 调整 PoolingHttpClientConnectionManagersetMaxTotalsetDefaultMaxPerRoute | | 并发线程过多 | 限制线程池大小,避免超过连接池容量 |


    ✅ 推荐修改后的完整示例代码

    import org.apache.http.client.config.RequestConfig;
    import org.apache.http.client.methods.CloseableHttpResponse;
    import org.apache.http.client.methods.HttpGet;
    import org.apache.http.impl.client.CloseableHttpClient;
    import org.apache.http.impl.client.HttpClients;
    import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
    import org.apache.http.util.EntityUtils;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Semaphore;
    
    public class TileDownloader {
    
        private static final int MAX_CONNECTIONS = 500;
        private static final Semaphore semaphore = new Semaphore(MAX_CONNECTIONS);
    
        public static void main(String[] args) {
            ExecutorService executor = Executors.newFixedThreadPool(1000); // 线程池可稍大
    
            for (int i = 0; i < 10000; i++) {
                executor.submit(() -> {
                    try {
                        semaphore.acquire(); // 控制并发
                        downloadTile("http://tileserver.com/tile/" + i);
                    } finally {
                        semaphore.release(); // 释放许可
                    }
                });
            }
    
            executor.shutdown();
        }
    
        private static void downloadTile(String url) {
            try (CloseableHttpClient httpClient = createClient();
                 CloseableHttpResponse response = httpClient.execute(new HttpGet(url))) {
                if (response.getStatusLine().getStatusCode() == 200) {
                    String content = EntityUtils.toString(response.getEntity());
                    // 处理返回内容
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        private static CloseableHttpClient createClient() {
            PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager();
            connManager.setMaxTotal(500);
            connManager.setDefaultMaxPerRoute(200);
    
            RequestConfig requestConfig = RequestConfig.custom()
                    .setConnectTimeout(5000)
                    .setSocketTimeout(5000)
                    .setConnectionRequestTimeout(5000)
                    .build();
    
            return HttpClients.custom()
                    .setConnectionManager(connManager)
                    .setRequestConfig(requestConfig)
                    .build();
        }
    }
    

    如需进一步优化性能,还可以考虑:

    • 异步下载(如使用 CompletableFuture)
    • 重试机制(处理网络不稳定)
    • 日志监控(记录失败请求)

    如需我帮助你集成这些功能,也可以继续提问!

    评论

报告相同问题?

问题事件

  • 创建了问题 1月30日