ConcurrentHashMap

一、基础结构与版本核心差异(高频!必会!)

JDK 1.7 实现:分段锁(Segment)

  • 运作原理
    将整个Map拆分为多个Segment(默认16个),每个Segment独立加锁(ReentrantLock)。
    写操作时只锁一个Segment,其他Segment仍可并发读写 → 降低锁冲突
  • 类比理解
    好比一栋楼有16个独立房间(Segment),每个房间有自己的锁。A在1号房存东西时,B仍可进2号房拿东西,互不影响。

JDK 1.8 实现:CAS + synchronized 细粒度锁

  • 运作原理
    • 取消Segment,结构变为 Node数组 + 链表 + 红黑树(同HashMap)
    • 锁粒度细化到单个桶的头节点(链表首节点或红黑树根节点)
    • 插入时:
      • 桶为空 → CAS无锁插入
      • 桶非空 → synchronized锁头节点再插入
  • 为何改用synchronized?
    JDK1.6后synchronized已优化(锁升级:无锁→偏向锁→轻量锁→重量锁),且节省内存(若用ReentrantLock需每个Node继承AQS,浪费资源)。

 二、6大核心机制与面试回答要点

1️⃣ Put流程(线程安全如何保证?)

  • 步骤
    ① 计算Key的hash值 → 定位桶位置;
    ② 若桶为空 → CAS插入(失败则重试);
    ③ 若桶为扩容中转节点(hash=-1) → 协助扩容;
    ④ 若桶非空 → synchronized锁头节点 → 遍历链表/树插入或更新;
    ⑤ 链表超8且数组≥64 → 转红黑树
  • 面试话术:“1.8的put通过CAS+桶锁实现安全:空桶无锁竞争用CAS,非空桶锁头节点防并发写。同时支持协助扩容,避免阻塞。”

2️⃣ Get流程(为何无需加锁?)

  • 原理:Node的valuenext指针用volatile修饰 → 写操作后其他线程立即可见最新值
  • 注意:与table数组是否volatile无关(volatile数组仅保证数组引用可见,不保证元素可见)。
  • 面试话术:“get无锁是因为Node内部用volatile保证可见性,读直接访问内存最新值,类似读Redis,无并发冲突。”

3️⃣ 扩容机制(如何高效?)

  • 触发条件:元素总数 ≥ 数组长度 × 0.75(负载因子固定0.75)。
  • 并发扩容
    • 线程扩容时发现其他线程在扩容 → 协助迁移数据(而非等待);
    • 迁移时用ForwardingNode标记已迁桶,写请求遇到该标记会协助扩容。
  • 面试话术:“扩容时线程可协同搬运数据,通过ForwardingNode标志让新请求也来帮忙,类似多人搬仓库货架,效率更高。”

4️⃣ Key/Value为何不允许null?

  • 二义性问题
    • map.get(key)返回null时 → 无法区分是“key不存在”还是“value本就是null”;
    • 多线程下:A线程读null时,B线程可能正好写入null → 破坏业务逻辑。
  • 面试话术:“null值在多线程场景产生二义性,比如缓存系统无法区分‘无数据’和‘数据为null’,因此直接禁止。”

5️⃣ Size计算(如何保证并发准确?)

  • 原理
    用 LongAdder分片计数思想
    • 维护baseCount + CounterCell[]分片计数器;
    • 更新时优先CAS改baseCount,失败则改分片计数器 → 分散并发压力
  • 面试话术:“size不是遍历计数,而是分片累加(baseCount+CounterCell数组),类似超市多个收银台独立计数再汇总,避免所有线程挤一个计数器。”

6️⃣ 迭代器:弱一致性(Weakly Consistent)

  • 原理:迭代器创建时不冻结快照,遍历期间可能读到其他线程新增/删除的条目(未遍历部分),但不保证完全实时210。
  • 对比:HashMap迭代器是强一致性(遍历中修改直接抛ConcurrentModificationException)。
  • 面试话术:“迭代器是弱一致的:遍历时可能看到后续修改,但不保证全量最新。类似边看书边有人修改内容——你翻到后面时会看到新内容,但已读过的页不重读。”

 三、JDK 1.7 vs 1.8 高频对比表

对比项JDK 1.7JDK 1.8优化点
数据结构Segment + HashEntryNode数组+链表+红黑树结构统一,查询效率提升
锁机制ReentrantLock(分段锁)CAS + synchronized(桶级锁)锁粒度更细,内存占用更低
并发度由Segment数量决定(默认16)由数组大小动态决定更灵活,减少竞争
扩容仅当前Segment扩容多线程协同扩容迁移效率大幅提升
哈希冲突处理链表链表→红黑树(阈值8)防链表退化,查询O(1)→O(logN)

 总结1.8优势:锁粒度细(桶级 vs 段级)、内存省(去Segment)、扩容快(协作式)、查询稳(链表转树)。


 四、致命细节(易挂点!)

  1. 树化条件:链表长度 > 8  数组长度 ≥ 64(否则优先扩容);
  2. size()非实时:分片计数有微小延迟,并发极高时可能误差;
  3. 并发度设置:1.7可设并发度(构造函数参数),1.8已取消。

 五、面试表达技巧

  • 原理 → 优点 → 对比三段式回答:“比如您问put如何线程安全(原理),1.8用CAS和桶锁实现,优点是冲突时只锁一个桶(优点),不像Hashtable全表锁,也不像1.7锁整个Segment(对比)。”
  • 主动关联场景:“像缓存系统——高并发读用volatile无锁get,写用synchronized仅锁单桶,比Collections.synchronizedMap效率高得多。”

散々雨に降られたって 笑っていられる 即使被雨水淋湿也能笑着面对。----孤独摇滚
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇