画出ConcurrentHashMap 1.8的put流程图,记住CAS和synchronized的结合

embedded/2025/3/22 13:52:29/

ConcurrentHashMap 1.8 put 操作流程

结构:数组 + 链表/红黑树。
并发控制:CAS(无锁操作) + synchronized(锁住桶头部节点)。
改进:相比1.7的分段锁(Segment),1.8锁粒度更细,仅锁住单个桶。

流程图(文字描述)

START↓
输入 key 和 value↓
计算 hash 值:hash = spread(key.hashCode()) // 高低位异或,减少冲突↓
获取数组长度 n = tab.length,计算索引 i = (n - 1) & hash↓
获取桶位置 tab[i] 的头节点 f = tabAt(tab, i) // 通过Unsafe.getObjectVolatile获取↓
[条件分支] f == null ?是 → 使用 CAS 插入新节点↓casTabAt(tab, i, null, new Node(hash, key, value)) // CAS尝试将null替换为新节点↓[成功] → 结束[失败] → 返回循环重试否 → 检查 f 的状态↓[条件分支] f.hash == -1 ? // -1表示正在扩容或初始化是 → 协助扩容:helpTransfer(tab, f)否 → 进入同步逻辑↓synchronized (f) { // 锁住桶头节点↓再次检查 tab[i] == f // 确保锁住的节点未变↓[链表操作]如果 f 是链表节点:遍历链表,查找 key↓[存在] → 更新 value[不存在] → 尾部插入新节点↓检查链表长度 >= 8 ? → treeifyBin() // 转红黑树[红黑树操作]如果 f 是红黑树节点:putTreeVal() // 红黑树插入↓} // synchronized 结束↓
更新 size:addCount(1L, binCount) // CAS更新元素计数,可能触发扩容↓
END

流程图关键点说明

1.CAS 使用:

初始化桶:当桶为空(tab[i] == null),用 casTabAt(基于Unsafe的CAS操作)插入新节点,无锁高效。
更新size:addCount 用 CAS 更新总元素数,避免多线程竞争。
示例代码:


static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i, Node<K,V> c, Node<K,V> v) {return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
2.synchronized 使用:

当桶不为空,锁住头节点 f,确保同一桶的并发操作线程安全。
锁粒度细化:只锁当前桶,不影响其他桶。
示例代码:

synchronized (f) {if (tabAt(tab, i) == f) {// 链表或红黑树操作}
}
3.CAS + synchronized 结合:
  • CAS 用于无竞争场景(如空桶插入、计数更新),减少锁开销。
  • synchronized 用于有竞争场景(如链表追加、红黑树操作),保证一致性。
  • 这种组合避免了1.7的全段锁,提高并发性能。

完整put方法源码(精简版)

final V putVal(K key, V value, boolean onlyIfAbsent) {int hash = spread(key.hashCode());int binCount = 0;Node<K,V>[] tab = table;for (;;) {int n = tab.length, i;Node<K,V> f;if (tab == null || n == 0)tab = initTable(); // 初始化表else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))break; // CAS插入成功} else if (f.hash == -1) // 扩容中tab = helpTransfer(tab, f);else {V oldVal = null;synchronized (f) { // 锁住头节点if (tabAt(tab, i) == f) {if (f.hash >= 0) { // 链表binCount = 1;for (Node<K,V> e = f;; ++binCount) {if (e.hash == hash && e.key.equals(key)) {oldVal = e.val;if (!onlyIfAbsent) e.val = value;break;}Node<K,V> pred = e;if ((e = e.next) == null) {pred.next = new Node<K,V>(hash, key, value);break;}}} else { // 红黑树TreeNode<K,V> p = (TreeNode<K,V>)f;oldVal = p.putTreeVal(this, tab, hash, key, value);}}}if (binCount != 0) {if (binCount >= TREEIFY_THRESHOLD)treeifyBin(tab, i); // 转红黑树if (oldVal != null)return oldVal;break;}}}addCount(1L, binCount); // 更新计数return null;
}

面试记录要点

1.流程简述:

计算hash -> 检查桶 -> CAS插入(空桶)或synchronized操作(非空桶) -> 更新size。

2.CAS作用:

空桶插入:casTabAt。
计数更新:addCount。

3.synchronized作用:

锁桶头节点,保证链表/红黑树操作安全。

4.性能优化:

CAS减少锁使用,synchronized细化锁粒度。

5.画图提示:

画一个数组,标注桶状态(空/链表/红黑树)。
用箭头表示CAS(空桶)和synchronized(链表操作)分支。

手绘流程图建议

  1. 顶层:输入key/value,计算hash。
  2. 分支1:tab[i] == null -> CAS插入 -> 结束。
  3. 分支2:tab[i] != null
    - 子分支1:f.hash == -1 -> 协助扩容。 子分支2:f.hash >= 0 ->
    - synchronized锁头节点 -> 链表/红黑树操作。
  4. 底部:更新size,结束。

口述练习:

“put 时先算 hash,桶为空用 CAS 插入;不为空用 synchronized 锁头节点,链表操作或转红黑树,最后 CAS 更新 size。”


http://www.ppmy.cn/embedded/174704.html

相关文章

MyBatis 的一次缓存与二次缓存

在数据访问层的开发中&#xff0c;MyBatis 是一款极为流行的持久层框架&#xff0c;它有效地简化了数据库操作的复杂性。而 MyBatis 的缓存机制更是其一大亮点&#xff0c;能够显著提升数据查询的效率&#xff0c;减少数据库的访问次数&#xff0c;从而优化系统性能。本文将深入…

【蓝桥杯速成】| 3.数据结构

题目一&#xff1a;两数之和 问题描述 1. 两数之和 - 力扣&#xff08;LeetCode&#xff09; 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。 你可以假设每种输入只会…

Linux top 命令详解:从入门到高级用法

Linux top 命令详解&#xff1a;从入门到高级用法 在 Linux 系统中&#xff0c;top 是一个强大的实时监控工具&#xff0c;用于查看系统资源使用情况和进程状态。它可以帮助你快速了解 CPU、内存、负载等信息&#xff0c;是系统管理员和开发者的日常利器。本文将从基本用法开始…

Retrofit中scalars转换html为字符串

简介 在Retrofit中&#xff0c;如果你想直接获取HTML或其他文本格式的响应内容而不是将其映射到一个模型类&#xff0c;ScalarsConverterFactory 就派上用场了。ScalarsConverterFactory 是一个转换器工厂&#xff0c;它能够将响应体转换为Java基本类型如String、Integer或Byte…

二分查找-在排序数组中查找元素的第一个和最后一个位置

34.在排序数组中查找元素的第一个和最后一个位置 给你一个按照非递减顺序排列的整数数组 nums&#xff0c;和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值 target&#xff0c;返回 [-1, -1]。你必须设计并实现时间复杂度为 O(…

C++从入门到实战(六)类和对象(第二部分)C++成员对象及其实例化,对象大小与this详解

C从入门到实战&#xff08;六&#xff09;类和对象&#xff08;第二部分&#xff09;C成员对象及其实例化&#xff0c;对象大小与this详解 前言一、类和对象里面成员变量&#xff0c;成员函数是什么1.1成员变量1.2成员函数1.3成员变量、成员函数与局部变量的对比 二、类的实例化…

音视频系列——Websockets接口封装为Http接口

模型服务示例&#xff1a;实时语音转文本服务 本示例展示一个支持双协议&#xff08;WebSocket流式接口HTTP同步接口&#xff09;的语音转文本模型服务&#xff0c;并提供将WebSocket接口封装为HTTP接口的代码实现。 一、服务架构设计 #mermaid-svg-nw0dMZ4uKfS4vGZR {font-fa…

代码随想录算法训练营第十五天 | 数组 |长度最小的子数组和螺旋矩阵II

长度最小的子数组 【题目简介】 【自写数组解法】 class Solution:def minSubArrayLen(self, target: int, nums: List[int]) -> int:minLength float(inf)slow 0fast 0cur_sum nums[slow]# 终止条件&#xff1a;fast不能超过最大索引值while slow < fast and fas…