You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swing Worker结合Guava RateLimiter执行过慢问题排查

问题分析与解决方案

你的核心问题是Swing Worker的doInBackground方法是单线程执行的,所以循环里的所有任务都是串行运行——必须等上一个market.initializeCandles()(API调用)完全结束,才会执行下一次循环迭代。哪怕加了RateLimiter,也只是在单线程里控制调用间隔,根本发挥不了“每秒18次并发调用”的作用,反而因为串行的API响应时间拖慢了整体速度,所以去掉RateLimiter后速度没变化。

要实现“并发执行任务+RateLimiter限流”,你需要结合线程池来处理并行任务,同时用RateLimiter控制每秒18次的API调用频率,具体步骤如下:

改进方案

  1. 创建线程池:用ExecutorService创建固定大小的线程池,负责并行执行API调用任务(线程数建议根据实际情况调整,比如8~16,避免过多并发触发API的反限流机制)
  2. 用RateLimiter控制全局调用频率:RateLimiter是线程安全的,多个线程调用acquire()会自动排队,保证每秒最多18次许可发放
  3. 用CountDownLatch等待所有任务完成:确保所有API调用都执行完后,再结束Swing Worker的后台任务
  4. 安全更新UI:通过publish()和process()方法在EDT线程更新进度条,避免线程安全问题

修改后的代码示例

@Override
protected Boolean doInBackground() throws Exception {
    tickers = rc.getAll24HrPriceStatistics();
    progressBar.setMaximum(tickers.size());
    
    // 创建线程池(根据实际情况调整线程数)
    ExecutorService executor = Executors.newFixedThreadPool(10);
    // 初始化CountDownLatch,计数等于任务总数
    CountDownLatch latch = new CountDownLatch(tickers.size());
    
    for (int i = 0; i < tickers.size(); i++) {
        int finalI = i;
        String symbol = tickers.get(i).getSymbol();
        
        executor.submit(() -> {
            try {
                // 先获取RateLimiter许可,控制每秒18次调用
                rateLimiter.acquire();
                
                Market market = new Market(screener, symbol, timeframe);
                market.initializeCandles(); // 执行API调用
                table.addMarket(market);
                
                // 发布进度到EDT线程更新UI
                publish(finalI);
            } catch (Exception e) {
                // 处理API调用异常,比如打印日志或记录错误
                e.printStackTrace();
            } finally {
                // 任务完成,计数减1
                latch.countDown();
            }
        });
    }
    
    // 等待所有任务执行完成
    latch.await();
    // 关闭线程池
    executor.shutdown();
    
    return true;
}

// 重写process方法,在EDT线程更新进度条
@Override
protected void process(List<Integer> chunks) {
    // 取最后一个进度值更新(避免多次更新重复值)
    int latestProgress = chunks.get(chunks.size() - 1);
    progressBar.setValue(latestProgress + 1); // 因为i从0开始,进度要+1
}

关键说明

  • 线程池大小:不要设置过大,比如如果API本身限制每秒18次调用,线程池设为10~15就足够,过多线程只会在RateLimiter处排队,浪费资源
  • RateLimiter的作用:它会保证全局每秒最多发放18个许可,不管有多少线程在请求,都能严格控制调用频率
  • CountDownLatch:用来阻塞doInBackground方法,直到所有API任务都执行完成,避免Swing Worker提前结束
  • UI更新安全:publish()会把进度数据提交到EDT队列,process()在EDT线程执行,所以更新进度条不会触发Swing线程安全问题

内容的提问来源于stack exchange,提问作者BitQueen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 00:20:45