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

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

相关推荐
SimonKing3 小时前
艹,维护AI写的代码,我心态崩了......
java·后端·程序员
用户298698530143 小时前
Java Word 文档样式进阶:段落与文本背景色设置完全指南
java·后端
小bo波19 小时前
从"任意文件复制"深挖Java I/O:字符流与字节流的本质抉择
java·nio·io流·后端开发·文件复制
nanxun8862 天前
记一次诡异的 Docker 容器"串包"故障排查
java
用户1563068103512 天前
Day01 | Java 基础(Java SE)
java
行者全栈架构师2 天前
Maven dependency:tree 的 8 个高级用法
java·后端
行者全栈架构师2 天前
IDEA 中 Maven 项目的 15 个红色报错快速解决方法
java·后端
令人头秃的代码0_02 天前
mac(m5)平台编译openjdk
java
唐青枫3 天前
Java JDBC 实战指南:从 Connection 到事务和连接池
java
一个做软件开发的牛马3 天前
MyBatis-Plus 从零实战:完整搭建可运行 Demo,BaseMapper 零 SQL、Wrapper 条件构造、分页插件与代码生成器详解
java·后端