如何通过多线程或异步调用优化ArrayList批量REST请求?
批量异步/多线程调用REST接口实现方案
针对单线程批量调用耗时过长的问题,下面给出两种Java实现方案,核心都是利用多线程/异步IO并行处理批量请求,缩短总耗时。
方案一:基于CompletableFuture的异步调用
利用Java 8+的CompletableFuture配合线程池,将每个批量REST调用包装成异步任务,并行执行后统一等待结果,适合需要异步回调或结果聚合的场景。
代码示例
import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; public class AsyncRestBatchProcessor { // 根据REST接口并发限制调整线程池大小,比如接口允许20并发就设为20 private static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(20); private static final int BATCH_SIZE = 100; public static void main(String[] args) throws InterruptedException { // 模拟10000条记录的ArrayList List<String> records = new ArrayList<>(10000); for (int i = 0; i < 10000; i++) { records.add("record_" + i); } // 分割成指定大小的批量 List<List<String>> batches = splitIntoBatches(records, BATCH_SIZE); List<CompletableFuture<Void>> futures = new ArrayList<>(); // 提交所有异步任务 for (List<String> batch : batches) { CompletableFuture<Void> future = CompletableFuture.runAsync(() -> { try { // 替换为实际REST接口调用逻辑 callRestApi(batch); } catch (Exception e) { // 单个批量任务异常处理,比如日志记录 System.err.println("批量任务失败,记录数:" + batch.size() + ",错误:" + e.getMessage()); } }, EXECUTOR); futures.add(future); } // 等待所有异步任务完成 CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); // 关闭线程池,释放资源 EXECUTOR.shutdown(); EXECUTOR.awaitTermination(1, TimeUnit.HOURS); } // 通用列表分割方法 private static <T> List<List<T>> splitIntoBatches(List<T> list, int batchSize) { List<List<T>> batches = new ArrayList<>(); for (int i = 0; i < list.size(); i += batchSize) { int end = Math.min(i + batchSize, list.size()); batches.add(new ArrayList<>(list.subList(i, end))); } return batches; } // 模拟REST接口调用 private static void callRestApi(List<String> batch) throws InterruptedException { // 实际场景替换为RestTemplate/WebClient等HTTP客户端调用 System.out.println("处理批量,记录数:" + batch.size() + ",线程:" + Thread.currentThread().getName()); // 模拟接口耗时(原单线程单批约9秒,这里用1秒演示) TimeUnit.SECONDS.sleep(1); } }
方案二:基于线程池的多线程批量处理
如果不需要异步回调逻辑,直接用ExecutorService提交Runnable任务,是更简洁的多线程实现方式。
代码示例
import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; public class ThreadPoolRestBatchProcessor { private static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(20); private static final int BATCH_SIZE = 100; public static void main(String[] args) throws InterruptedException { List<String> records = new ArrayList<>(10000); for (int i = 0; i < 10000; i++) { records.add("record_" + i); } List<List<String>> batches = splitIntoBatches(records, BATCH_SIZE); // 提交所有批量任务到线程池 for (List<String> batch : batches) { EXECUTOR.submit(() -> { try { callRestApi(batch); } catch (Exception e) { System.err.println("批量任务失败:" + e.getMessage()); } }); } // 关闭线程池并等待所有任务完成 EXECUTOR.shutdown(); if (!EXECUTOR.awaitTermination(1, TimeUnit.HOURS)) { // 超时未完成时强制关闭 EXECUTOR.shutdownNow(); } } private static <T> List<List<T>> splitIntoBatches(List<T> list, int batchSize) { List<List<T>> batches = new ArrayList<>(); for (int i = 0; i < list.size(); i += batchSize) { int end = Math.min(i + batchSize, list.size()); batches.add(new ArrayList<>(list.subList(i, end))); } return batches; } private static void callRestApi(List<String> batch) throws InterruptedException { System.out.println("处理批量,记录数:" + batch.size() + ",线程:" + Thread.currentThread().getName()); TimeUnit.SECONDS.sleep(1); } }
关键注意事项
- 线程池大小:需根据REST接口的并发限制、服务器资源调整,避免过大导致接口限流或服务器过载。比如接口允许20并发,线程池核心/最大线程数就设为20。
- 异常处理:每个批量任务内部必须捕获异常,防止单个任务失败中断整个流程。
- 资源释放:必须调用
executor.shutdown()和awaitTermination()关闭线程池,避免JVM无法正常退出。 - 实际HTTP调用:将示例中的
callRestApi替换为实际客户端实现,比如用WebClient(异步非阻塞)可进一步提升性能。
内容的提问来源于stack exchange,提问作者Ritesh Agrawal
相关产品推荐
相关产品推荐

