ConcurrentHashMap 1.7 vs 1.8:分段锁到 CAS+红黑树的演进与性能差异

原文来自于:zha-ge.cn/java/78

ConcurrentHashMap 1.7 vs 1.8:分段锁到 CAS+红黑树的演进与性能差异

讲真,老程序员都得靠点岁月滤镜。我第一次深扒 ConcurrentHashMap 的源码,还是在 JDK 1.7 时代,那时候的哈希并发魂就是"分段锁",每次和同事聊起,都有点"这玩意多神"的尬自豪。结果等到 1.8 出来,我突然尴尬地发现------这货都变天了!今天,就和大家唠唠我踩过的那些并发哈希坑。


老 ConcurrentHashMap 的分段锁江湖

Java 1.7 的 ConcurrentHashMap 纯粹是"分治思想"的现实写照。它把整个 Map 切成多个 Segment,每个 Segment 都是个小 HashTable,管自己一亩三分地。你 put、get、remove,基本不会打架:

  • 分段锁(Segment):每段一把锁,减少竞争
  • 不支持 null key/null value(冷知识)

比如放数据,大致长这样:

java 复制代码
int hash = hash(key);
int segmentIndex = (hash >>> segmentShift) & segmentMask;
Segment<K,V> s = segments[segmentIndex];
s.lock();
try {
    s.put(key, value);
} finally {
    s.unlock();
}

简化后的意思就是:我锁的只是 Segment,不像 HashTable 那样整个表锁死(所以 HashTable 几乎没人用了...)。


JDK 1.8:演变新世界,CAS+链表+红黑树

到了 1.8,这玩意彻底变了个腔调,"分段锁"拜拜,取而代之的是"桶级别"的操作+一身并发黑科技:

  • 不再有 Segment 数组,只有 Node[] table
  • put 的时候,先 CAS 尝试抢占 Node(用 synchronized 兜底)
  • 链表太长自动转红黑树,查找 O(logn) 不是梦

来,来,看核心放数据的套路:

java 复制代码
Node<K,V>[] tab = table;
int i = (n - 1) & hash;
Node<K,V> f = tabAt(tab, i);
if (f == null) {
    if (casTabAt(tab, i, null, newNode)) {
        // CAS抢坑成功,无竞争~
    }
} else {
    synchronized (f) {
        // 操作链表或红黑树,争抢得激烈则自动变树!
    }
}

注:这 tabAtcasTabAt 都是 Unsafe 的骚操作~


踩坑瞬间

我自己曾经经历过一次性能"大地震"。那会儿线上压测,两个不同 JDK 下 ConcurrentHashMap,居然结果天差地别。

痛点回忆:

  • 1.7 多线程 put,性能稳定,但线程数过高还是得拼命锁各个段
  • 1.8 急剧提升高并发下的写吞吐,尤其线程超多时,老版本直接锁段,等得人抓耳挠腮,新版 CAS 猛冲
  • 红黑树救过命:有一回 keys 奇葩碰撞扎堆,1.7 直接挂了链表超长数秒。1.8 自动转树,时间稳定 O(logn),查找都不带卡的

有次还诡异碰到过遍历 ConcurrentHashMap 一边 put 的场景,1.7 下因为 Segment 结构脑壳痛,1.8 基本溜了。


性能小对比

偷偷摸鱼做了下对比:

JDK 高并发写延迟 键大量冲突 遍历一致性
1.7 分段锁 查找慢 易被锁阻塞
1.8 CAS+树 稳定O(logn) 比较优秀

简单一句话:1.7 读写分段锁,跑满高速路还堵车;1.8 脱胎换骨,红绿灯智能变道,加点"算法调度",体验大变脸!


经验启示

  • 多线程首选 1.8 及以上,不用 segment 锁省心
  • Async 大量写入/高冲突 key,红黑树才是真香代码
  • 不想卡链表查找?JDK 1.8 后不怕碰撞怪兽
  • 想偷懒?1.8 遍历、并发用法更随心,不用操心锁分段

唉,老了,偶尔还是怀念 Segment 那点"老锁情怀",但终究得向先进技术低头。也许代码本来也是进化史------每一版改动后,费神琢磨"为啥这样",也就有了新技能傍身。大家有啥并发"笑话",咱评论区扯一扯呗?

写到这儿,键盘都热了,今天就聊到这儿吧。忙到凌晨的程序员,才能继续踩下一个坑不是?

相关推荐
间彧2 小时前
复用线程:原理详解与实战应用
java
咖啡Beans3 小时前
使用OpenFeign实现微服务间通信
java·spring cloud
我不是混子3 小时前
说说单例模式
java
间彧5 小时前
SimpleDateFormat既然不推荐使用,为什么java 8+中不删除此类
java
间彧5 小时前
DateTimeFormatter相比SimpleDateFormat在性能上有何差异?
java
间彧6 小时前
为什么说SimpleDateFormat是经典的线程不安全类
java
MacroZheng6 小时前
横空出世!MyBatis-Plus 同款 ES ORM 框架,用起来够优雅!
java·后端·elasticsearch
用户0332126663676 小时前
Java 查找并替换 Excel 中的数据:详细教程
java