
本文介绍一种基于 concurrenthashmap 与 future 的轻量级方案,确保多个线程对同一 requestid 的任务串行执行,不同 requestid 则可并行处理,兼顾性能与正确性。
本文介绍一种基于 concurrenthashmap 与 future 的轻量级方案,确保多个线程对同一 requestid 的任务串行执行,不同 requestid 则可并行处理,兼顾性能与正确性。
在高并发请求处理系统中,常需保证「相同业务标识(如 RequestID)的任务不被并发执行」,而不同标识的任务应尽可能并行以提升吞吐。Long 类型的 RequestID 作为典型业务键,其并发控制不能依赖全局锁(牺牲吞吐),也不宜为每个 ID 预分配独立锁对象(内存与 GC 压力大)。理想方案应满足:按需创建、自动回收、无死锁、低开销。
上述代码提供了一个简洁可靠的实现:RequestProcessor<t></t> 类利用 ConcurrentHashMap<long future>></long> 作为“进行中请求注册表”,以 requestId 为 key,存储对应任务的 Future 引用。关键逻辑在于 inProgressRequestIds.compute(...) 的原子计算操作:
inProgressRequestIds.compute(
task.getRequestId(),
(key, existingFuture) -> {
task.setResult(retrieveResultOf(executor.submit(task)));
return null; // 移除旧 Future(若存在),避免内存泄漏
}
);该设计巧妙地将“锁获取”与“任务提交”合二为一:
- 若
requestId未被占用(existingFuture == null),则直接提交任务并返回结果; - 若已被占用(
existingFuture != null),compute仍会执行 lambda,但此时existingFuture代表前序未完成任务——注意:原实现未显式等待或拒绝,而是直接提交新任务并覆盖。实际生产中建议增强为阻塞等待或快速失败策略(见下文注意事项)。
retrieveResultOf() 方法采用循环 future.get() 处理中断,确保线程中断状态被正确恢复,符合 Java 并发最佳实践。
✅ 优势总结:
- 无显式
synchronized或ReentrantLock,避免锁竞争瓶颈; -
ConcurrentHashMap.compute()提供 O(1) 原子性,天然支持高并发注册/查询; - 泛型
RequestProcessor<t></t>支持任意返回类型任务; -
ExecutorService可灵活替换(如固定线程池、ForkJoinPool)。
⚠️ 注意事项与改进建议:
-
任务排队策略:当前实现对重复
requestId会“后提交者覆盖前任务”,若需严格串行(即新任务排队等待前任务完成),应改为:检查existingFuture != null时调用existingFuture.get()阻塞等待,再提交新任务; -
内存清理:
compute中返回null会移除 entry,但若任务异常退出且未被get()消费,Future可能残留。建议配合ScheduledExecutorService定期清理超时Future; -
Shutdown 安全:如答案注释所述,
RequestProcessor本身不管理ExecutorService生命周期,调用方需确保executor.shutdown()被正确调用,避免线程泄露; -
ID 散列优化:
Long作为ConcurrentHashMapkey 性能优异,无需额外包装。
综上,该方案以极简代码实现了基于业务键的细粒度并发控制,是分布式 ID 场景下线程安全任务调度的优秀范例。

















