阿里巴巴为何重写HashMap?ConcurrentHashMap在高并发下竟有这些致命缺陷
作者:佚名 时间:2025-11-11 01:26
身为一名长时间关注互联网技术发展历程的行业观察者,我察觉到头部企业于基础组件方面的自主研发实践常常意味着技术发展的新趋向,阿里巴巴针对HashMap的重新构建,恰是业务规模逼迫技术深入进展的颇为典型的例子,这值得众多开发者深入探究分析。
分段锁的进化路径
数据被JDK的ConcurrentHashMap采用分段锁机制划分为多个Segment独立加锁,在并发线程数可控的场景下这种设计表现稳定,每个Segment锁管理特定哈希区间的数据访问, 。
一旦并发线程数冲破万级这一界限,锁竞争频率遂以指数级的态势增长。依据某电商平台所提供的监控数据表明,在2023年双11那个时段,配置中心所运用的ConcurrentHashMap出现了每秒超过200次的锁等待情形,从而致使配置推送延迟最长达到了5秒。
内存结构的优化实践
// 看似正常的缓存实现
private final ConcurrentHashMap userCache =
new ConcurrentHashMap<>(10_000_000);
public UserProfile getUserProfile(String userId) {
return userCache.computeIfAbsent(userId, this::loadFromDB);
}
符合标准的HashMap的Node节点运用固定结构,每个节点非得要存储hash参数、key实例、value数值以及next指针,在存储高达数千万个键值对之际,仅仅是节点对象自身便会占据超多的堆内存空间 。
有一个金融科技方面的团队,在2024年1月所进行的性能测试里发现,当存储5000万用户数据之际,ConcurrentHashMap致使年轻代的GC频率增长了3倍,每一次GC的停顿时间从30ms延伸到了120ms,对交易系统的响应速度造成了严重的影响 。
// 看似无害的配置更新
private final ConcurrentHashMap configMap =
new ConcurrentHashMap<>();
public void updateConfig(String key, String value) {
configMap.put(key, value); // 看起来很正常
}
渐进式扩容机制
传统的HashMap,于扩容之际,需一次性达成全部数据的迁移,而这般过程,会在长时间范畴内占有锁资源的。当数据量抵达TB级别之时,扩容经过也许会持续数分钟,在此期间,服务大体上处于不可用的状态之中。
阿里自己研发的HashMap引进了渐进式迁移的一种策略,此策略会把扩容的进程给分解成多个小的任务,然后再分开一批跟着一批地去执行。这个策略在每次数据进行访问的时候仅仅去迁移其中部分的bucket,以此来保证服务在扩容的那些期间依旧能够维持超过70%的处理的能力。 。
// 类似阿里内部的分段策略
public class SegmentedHashMap {
private final Segment[] segments;
private final int segmentMask;
public V put(K key, V value) {
int hash = hash(key);
int segIndex = (hash >>> 28) & segmentMask;
return segments[segIndex].put(key, value, hash);
}
// 每个segment独立扩容,避免全局锁
static class Segment {
private volatile HashEntry[] table;
// ...
}
}
热点数据访问方案
// 根据业务特点预估容量
public class PreSizedConcurrentMap extends ConcurrentHashMap {
public PreSizedConcurrentMap(int expectedSize, float loadFactor) {
// 计算合适的初始容量,避免扩容
super(calculateInitialCapacity(expectedSize, loadFactor), loadFactor);
}
private static int calculateInitialCapacity(int expected, float lf) {
return (int) Math.ceil(expected / lf);
}
}
于高并发情形之下,部分热点数据的访问频次或许会占到总访问数量的80%还要多。如此一来,会致使特定bucket的链表长度迅猛增长,查寻效率由O(1)转变为O(n) 。
把一致性哈希算法予以引入,让热点数据在不同bucket里均匀分散开来。经过实测的数据表明,此方案致使2023年淘宝双11核心系统的查询性能提高了40%,其峰值QPS能够达到300万次每秒。
对象池复用策略
垃圾回收器会因频繁的节点创建以及销毁而承受巨大压力,尤其是在存在高并发写入的场景当中,年轻代对象的分配速率对系统吞吐量上限起着决定作用 。
运用对象池技术预先分配Node节点,以此来降低年轻代GC频率。某个物流平台于2024年3月接入优化版本之后,年轻代GC次数由每小时200次下降至50次,系统吞吐量提高了25%。
业务适配的架构思考
public class ProgressiveHashMap {
private volatile Table oldTable;
private volatile Table newTable;
private final AtomicInteger migrationIndex = new AtomicInteger(0);
public V get(K key) {
// 先查新表,再查旧表
V value = newTable.get(key);
if (value == null && oldTable != null) {
value = oldTable.get(key);
// 顺便迁移一个bucket
migrateBucket();
}
return value;
}
private void migrateBucket() {
// 每次操作迁移少量数据,分摊扩容成本
// ...
}
}
技术选型要和业务特征紧密关联,通用的组件常常没法满足特定场景的那种极致要求,阿里巴巴的业务规模使得其对于性能、稳定性的要求比常规标准高很多。
自行研究开发的 HashMap 针对电子商务业务所具有的高并发以及大数据量特点展开专项优化,这给开发者带来启示,当业务规模抵达一定量级的时候,对基础组件的定制化改造会成为必然的选择。
诸位开发者于实际工作期间有无碰到ConcurrentHashMap的性能瓶颈状况?关于高并发情形之下的性能挑战方面,你们采取了何种优化举措来加以应对?欢迎于评论区域分享你们的实战经验以及技术见解,要是感觉本文对你们有所启发,请点赞支援并且分享给更多同行 。
// 优化前:简单粗暴的缓存
private final Map cache = new ConcurrentHashMap<>();
// 优化后:分层缓存 + 预分配
public class OptimizedCache {
private final Map hotData; // 热点数据,小容量
private final Map coldData; // 冷数据,大容量
public OptimizedCache(int hotSize, int coldSize) {
// 根据访问模式预分配容量
this.hotData = new ConcurrentHashMap<>(hotSize, 0.9f);
this.coldData = new ConcurrentHashMap<>(coldSize, 0.75f);
}
public V get(K key) {
V value = hotData.get(key);
if (value != null) return value;
value = coldData.get(key);
if (value != null) {
// 热点数据提升策略
promoteToHot(key, value);
}
return value;
}
}




