java大批量list处理需分片、并发、控制三者协同:用lists.partition安全分片,线程池按cpu/io类型合理配置,结果通过超时机制和线程安全容器及时收集。

Java中处理大批量List数据,核心是“分片 + 并发 + 控制”,不是单纯开更多线程,而是让每一步都可控、可测、不越界。
分片要准:避免 subList 的陷阱
用 list.subList(from, to) 切片方便,但要注意它返回的是原列表的视图——修改子列表会直接影响原列表,且多线程并发读写时可能引发 ConcurrentModificationException 或数据错乱。安全做法是显式复制:
- 对只读场景(如批量查询、计算),subList 可接受,但需确保原始 list 不被其他线程修改
- 对需写入或转换的场景(如 map 转 DTO、加解密),务必用
new ArrayList(subList)或subList.stream().toList()(Java 16+)生成新副本 - 推荐使用 Guava 的
Lists.partition(list, size),内部已做防御性拷贝,代码简洁且线程安全
线程池要稳:别让 coreSize = batchCount
常见误区是按“总条数 ÷ 每批大小 = 线程数”来设 newFixedThreadPool(n)。这会导致线程数随数据量暴涨,比如 100 万条、每批 1000 条 → 启 1000 个线程,极易打满 CPU 和 GC 压力。
- 线程数应基于 CPU 核心数与任务类型权衡:CPU 密集型建议
core = Runtime.getRuntime().availableProcessors();IO 密集型可设为2–4 × core - 用
ThreadPoolExecutor替代Executors工厂方法,明确指定队列容量(如new LinkedBlockingQueue(100))和拒绝策略(如CALLER_RUNS_POLICY),防内存溢出 - 每批次任务应封装为独立
Callable或Runnable,避免闭包持有大对象引用
结果要收:别卡在 get() 上等全部完成
调用 invokeAll() 或循环 future.get() 会阻塞主线程,且一个任务超时或失败,后续 get() 仍会等待——影响整体响应时间。
- 用
invokeAll(tasks, timeout, unit)设全局超时,超时后主动 cancel 未完成任务 - 对非关键任务,可改用
CompletableFuture.supplyAsync(..., executor)链式处理,失败时 fallback,不阻塞主流程 - 若需汇总结果,建议每个任务处理完立即写入线程安全容器(如
ConcurrentLinkedQueue或CopyOnWriteArrayList),而非攒着等所有 future 返回
实战建议:三步落地不踩坑
一个轻量、健壮、易维护的分片并发处理模板:
-
第一步:用
Lists.partition(data, 500)分片(500 是 IO 类任务较稳妥的起点) - 第二步:提交到预设好参数的线程池(如 8 核机器配 core=8、max=16、queue=64)
-
第三步:用
CountDownLatch或CompletableFuture.allOf()协调完成信号,日志记录各批次耗时与异常,不依赖顺序返回
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











