concurrenthashmap 通过分段锁(jdk 8+ 为 cas + synchronized 单 node 锁)实现读无锁、写低冲突;注意 computeifabsent 原子性、size() 非 o(1)、不支持 null、慎用 join()/get()、显式指定线程池、缩小锁粒度、优先使用原子类及合理配置混合型线程池。

用 ConcurrentHashMap 替代 HashMap + synchronized
高频读写共享 Map 时,加锁整个 HashMap 是典型瓶颈。不是所有线程都在改结构,但锁让所有读也排队。
直接换 ConcurrentHashMap:它把桶数组分段加锁(JDK 8+ 改为 CAS + synchronized 锁单个 Node),读操作完全无锁,写冲突概率大幅下降。
- 注意
computeIfAbsent是原子的,适合缓存加载场景;但别在 lambda 里做耗时操作,会阻塞该 bucket 的其他写 -
size()不是 O(1),它要遍历所有 segment 计数,高并发下慎用;需要精确大小可考虑用LongAdder单独维护 - 不支持
null作为 key 或 value,这点和HashMap不同,运行时报NullPointerException
避免在 CompletableFuture 链中混用 join() 和 get()
常见错误是在异步流里突然调用 join() 或 get(),导致当前线程阻塞,吞掉线程池资源,尤其在 Tomcat 或 Netty 环境下极易引发请求堆积。
正确做法是用 thenApply、thenCompose 向下传递结果,保持非阻塞链式调用:
立即学习“Java免费学习笔记(深入)”;
CompletableFuture<String> task = CompletableFuture.supplyAsync(() -> fetchFromDB())
.thenCompose(data -> callExternalAPI(data))
.thenApply(result -> transform(result));
- 若必须等结果(如单元测试),只在最外层用
join();生产代码中几乎不该出现 - 指定线程池:不要依赖默认
ForkJoinPool.commonPool(),尤其当任务含 I/O 时,用supplyAsync(() -> ..., executor)显式传入 IO 专用线程池 -
exceptionally()和handle()才是处理异常的推荐方式,别靠外围 try-catch 包裹join()
慎用 synchronized 方法,优先缩小锁粒度
public synchronized void update() { ... } 锁的是当前实例,哪怕方法只改一个字段,整个对象都不可重入——这是隐藏的串行点。
更合理的做法是锁定具体资源或状态变量:
- 用私有 final 对象作锁:
private final Object lock = new Object();,在关键块中synchronized(lock) { ... } - 对集合操作,优先封装成线程安全类型(如
Collections.synchronizedList),而不是每次手动同步整个方法 - 计数类场景直接用
AtomicInteger或LongAdder,比synchronized块快一个数量级,且无锁竞争
线程池配置不能只看 CPU 核数
很多人照搬 “CPU 核数 + 1” 公式配 ThreadPoolExecutor,结果在混合型服务(既有计算又有 DB/HTTP 调用)里频繁触发拒绝策略或队列爆炸。
真实配置得拆开看任务类型:
- CPU 密集型(如加解密、图像处理):核心线程数 ≈ CPU 核数,队列用
SynchronousQueue,避免任务积压 - I/O 密集型(如数据库查询、远程调用):核心线程数可设为 2×CPU 核数甚至更高,配合
LinkedBlockingQueue缓冲突发流量 - 务必设置
ThreadFactory命名线程,否则线程 dump 里全是pool-1-thread-42,无法定位归属模块
真正难的是识别任务混合比例——一次慢 SQL 可能让整个线程池卡死,所以监控 activeCount、queueSize 和 GC 暂停时间比调参更重要。











