ConcurrentHashMap添加一个键值对的过程是怎么样的?

1 个回答

  • 林浩
    莫愁前路无知己,天下谁人不识君。
    ### ConcurrentHashMap 添加键值对的过程 ![image.png](https://images.codewd.com/72d4e129a940a316f01214183f1c4ec3.jpg "image.png") #### 1. 判断 `null` 值 在 `ConcurrentHashMap` 中,`key` 和 `value` 都不允许为 `null`,如果有任何一个为 `null`,将会抛出 `NullPointerException`。 #### 2. 计算 `hash` 根据 `key` 计算 `hash` 值,计算方法与 `HashMap` 基本一致,但实现上有所不同。 #### 3. 进入 `for` 循环,插入或更新元素 1. **初始化 `table`**: - 如果 `table` 为 `null` 或 `table.length` 为 `0`,表示数组未初始化,则调用 `initTable()` 方法进行初始化。在 `initTable` 方法中,通过 CAS 操作对 `SIZECTL` 变量赋值为 `-1`,确保只有一个线程可以初始化 `table`,其他线程调用 `Thread.yield()` 让出时间片。 2. **插入新节点**: - 如果 `f == null`,表示当前下标没有哈希冲突的键值对,则使用 CAS 操作在当前数组下标处插入新节点,并退出 `for` 循环。 3. **处理扩容中的节点**: - 如果 `f != null && f.hash == MOVED`,表示正在进行扩容,当前节点 `f` 是扩容标志节点,则调用 `helpTransfer()` 方法来协助扩容,然后继续 `for` 循环。 4. **处理链表或红黑树**: - 如果上述条件都不满足,则表示当前下标处存储的是链表或红黑树。此时对 `f` 进行 `synchronized` 同步锁操作,执行以下判断: - **链表**:如果 `f.hash > 0`,表示当前存储的是链表。遍历链表,如果找到相同 `key` 的节点则更新 `value`,否则在链表末尾插入新节点。如果链表长度超过8且数组长度大于64,则调用 `treeifyBin()` 方法将链表转换为红黑树。 - **红黑树**:如果 `f` 是 `TreeBin` 类型,表示当前存储的是红黑树,调用 `putTreeVal()` 方法插入或更新节点。 #### 4. 判断是否需要扩容 最后,调用 `addCount()` 方法增加元素计数,并判断是否超过扩容阈值 `sizeCtl`。如果超过,则进行扩容操作;如果正在扩容,当前线程会辅助扩容。 这个过程涉及到复杂的并发控制,通过 CAS 操作和 `synchronized` 锁来保证线程安全,同时在必要时对链表进行树化处理,以优化性能。 ConcurrentHashMap源码: ```java final V putVal(K key, V value, boolean onlyIfAbsent) { //ConcurrentHashMap不允许key和value为null,否则抛出异常 if (key == null || value == null) throw new NullPointerException(); int hash = spread(key.hashCode());//计算hash值 int binCount = 0; for (Node<K,V>[] tab = table;;) {//进入循环 Node<K,V> f; int n, i, fh; if (tab == null || (n = tab.length) == 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, null)))//如果发现此数组下标下没有哈希冲突的元素,就直接使用CAS操作将Node设置到此下标下 break; } else if ((fh = f.hash) == MOVED)//代表当前下标的头结点是标识节点,代表数组在扩容 tab = helpTransfer(tab, f);//协助扩容 else {//这种是普遍情况,存的是链表或者红黑树,进行插入 V oldVal = null; synchronized (f) {//加同步锁,避免多线程进行插入 if (tabAt(tab, i) == f) { if (fh >= 0) {//头结点hash值大于0,此数组下标下代表存的是一个链表 binCount = 1; for (Node<K,V> e = f;; ++binCount) {//遍历链表 K ek; if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) { 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, null); break; } } } else if (f instanceof TreeBin) {//下标下存的是红黑树 Node<K,V> p; binCount = 2; if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value)) != null) { oldVal = p.val; if (!onlyIfAbsent) p.val = value; } } } } if (binCount != 0) { if (binCount >= TREEIFY_THRESHOLD)//链表长度>=8,转换为红黑树 treeifyBin(tab, i); if (oldVal != null) return oldVal; break; } } } addCount(1L, binCount); return null; } ```
    发布于 2024-08-23 23:32 •IP属地 江苏
    举报
    评论
    收藏