在Java里如何选择合适的并发工具类_Java并发API选型说明

CountDownLatch适用于一次性等待(如服务初始化),CyclicBarrier支持重复使用(如多轮分片计算);computeIfAbsent不可用于高并发IO操作;thenCompose用于扁平化CompletableFuture嵌套;ThreadLocal在线程池中必须手动remove以防内存泄漏。

CountDownLatch 还是 CyclicBarrier?看是否需要复用

两者都用于线程等待,但语

义和生命周期完全不同。CountDownLatch 是一次性门闩:计数归零后所有等待线程被释放,之后再调用 await() 会立即返回;CyclicBarrier 支持重复使用,每次拦截满指定数量线程后自动重置。

常见错误是把 CountDownLatch 当作循环同步点——比如在每轮批处理中反复 await(),结果第二轮直接跳过,逻辑错乱。

  • CountDownLatch:启动阶段等 N 个服务初始化完成(只等一次)
  • CyclicBarrier:多线程分片计算,每轮需全部线程到达后再统一汇总(如迭代算法、模拟步进)
  • CyclicBarrier 可选传入 Runnable,在每次屏障触发时执行一次全局动作(如日志、状态检查)

ConcurrentHashMapcomputeIfAbsent 别在高并发下当“懒加载锁”用

这个方法看似能原子地判断 + 插入,但它的实现不是无锁的:内部会锁住对应桶(bin),若计算逻辑耗时(比如远程调用、IO),会导致该哈希桶长期被阻塞,拖慢其他 key 的读写。

典型误用:

cache.computeIfAbsent(key, k -> fetchFromDatabase(k));
在高并发查缓存场景下,可能引发大量线程在同一个桶上排队。

  • 真正适合的场景:计算轻量、无副作用(如构建一个简单对象、解析固定字符串)
  • 含 IO 或不确定耗时的操作,应先用 get() 判断,再用显式锁(如 ReentrantLock)或 putIfAbsent() + 双重检查
  • JDK 9+ 的 computeIfAbsent 对递归调用做了防护,但不解决性能阻塞问题

CompletableFuture 链式调用里,thenApplythenCompose 混用导致嵌套 CompletableFuture

这是最常踩的类型陷阱。如果异步操作本身返回 CompletableFuture,却用了 thenApply,结果会是 CompletableFuture>,后续 join() 会抛 ClassCastException 或死锁。

正确做法是:返回 CompletableFuture 就必须用 thenCompose(它会“压平”一层);返回普通值才用 thenApply

  • thenApply(x -> service.doSync(x)) → 返回 T,用 thenApply
  • thenApply(x -> service.doAsync(x)) → 返回 CompletableFuture,必须改 thenCompose
  • 漏掉这层判断,调试时看到 java.util.concurrent.CompletableFuture@12345678 打印出来,基本就是嵌套了

ThreadLocal 在线程池里不清理,内存泄漏比想象中来得快

很多人以为只在 ThreadLocal.set() 后忘记 remove() 才泄漏,其实更隐蔽:只要 ThreadLocal 实例本身是静态的(比如单例工具类里的 private static final ThreadLocal),而线程来自线程池(如 Executors.newFixedThreadPool),那么每个工作线程的 ThreadLocalMap 就会长期持有该变量的强引用,且 key 是弱引用 —— key 被回收后,value 却滞留,形成“幽灵引用”,GC 清不掉。

  • 所有在线程池中使用的 ThreadLocal,必须在任务结束前调用 remove()(推荐放在 try-finally 块里)
  • 不要依赖 initialValue() 自动创建,它只在第一次 get() 时触发,无法保证后续复用时状态干净
  • Spring 的 RequestContextHolder 等封装已内置清理,但自定义 ThreadLocal 必须手动管
很多选型问题本质不是 API 功能不够,而是没看清它的生命周期约束和隐含成本。比如 CyclicBarrier 的重入代价、computeIfAbsent 的桶级锁粒度、CompletableFuture 的泛型嵌套规则、ThreadLocal 在线程复用场景下的引用链残留——这些不在文档首页,但在压测或上线后突然冒头。