这么回答面试官之--ConcurrentHashMap如何get?为何无需加锁

ConcurrentHashMap的get操作的步骤如下:

  1. 通过key计算hash值
  2. 如果通过hash值判断key对应的节点是否在Node数组中,如果在则返回对应的值。此处分为3种情况:
    1. 如果恰好是数组元素,也即没有hash值计算得到的桶内只有一个节点,返回改节点的值;
    2. 如果是红黑树,则通过树的遍历方式去获取,然后返回值;
    3. 如果是普通链表,则通过链表的方式去获取。
  3. 如果通过hash值计算定位到的桶位置上没有元素,则返回null。

注意:ConcurrentHashMap的get操作并没有像put操作一样有CAS和synchronized锁。get操作不需要加锁,因为 Node 的元素 value 和指针 next 是用 volatile 修饰的,所以在多线程的环境下,即便value的值被修改了,在线程之间也是可见的。

public V get(Object key) {
        Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
        // 根据key计算hashcode
        int h = spread(key.hashCode());
        // key对应的节点在数组内
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (e = tabAt(tab, (n - 1) & h)) != null) {
            // 如果恰好在数组内,直接返回
            if ((eh = e.hash) == h) {
                if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                    return e.val;
            }
            // 这一块需要注意
            // 当hash值为负值的时候,表明正在进行扩容操作,这个时候查的是ForwardingNode的find方法来定位到nextTable
            // eh=-1,说明该节点是一个ForwardingNode,正在迁移,这时候调用ForwardingNode的find方法去nextTable里找
            // eh=-2,说明该节点是一个TreeBin,也就是红黑树节点,这时候会调用TreeBin的find方法遍历红黑树,由于红黑树有可能正在旋转变色,所以find里会有读写锁
            // static final int MOVED     = -1; // hash for forwarding nodes
            // static final int TREEBIN   = -2; // hash for roots of trees
            else if (eh < 0)
                return (p = e.find(h, key)) != null ? p.val : null;
            // 如果是普通链表,按照链表的方式获取
            while ((e = e.next) != null) {
                if (e.hash == h &&
                    ((ek = e.key) == key || (ek != null && key.equals(ek))))
                    return e.val;
            }
        }
        // key对应的节点不在数组内则返回null
        return null;
    }
ForwardingNode.find方法

Node<K,V> find(int h, Object k) {
            // loop to avoid arbitrarily deep recursion on forwarding nodes
            outer: for (Node<K,V>[] tab = nextTable;;) {
                Node<K,V> e; int n;
                if (k == null || tab == null || (n = tab.length) == 0 ||
                    (e = tabAt(tab, (n - 1) & h)) == null)
                    return null;
                for (;;) {
                    int eh; K ek;
                    if ((eh = e.hash) == h &&
                        ((ek = e.key) == k || (ek != null && k.equals(ek))))
                        return e;
                    if (eh < 0) {
                        if (e instanceof ForwardingNode) {
                            tab = ((ForwardingNode<K,V>)e).nextTable;
                            continue outer;
                        }
                        else
                            return e.find(h, k);
                    }
                    if ((e = e.next) == null)
                        return null;
                }
            }
}
TreeBin.find方法

final Node<K,V> find(int h, Object k) {
            if (k != null) {
                for (Node<K,V> e = first; e != null; ) {
                    int s; K ek;
                    if (((s = lockState) & (WAITER|WRITER)) != 0) {
                        if (e.hash == h &&
                            ((ek = e.key) == k || (ek != null && k.equals(ek))))
                            return e;
                        e = e.next;
                    }
                    else if (U.compareAndSwapInt(this, LOCKSTATE, s,
                                                 s + READER)) {
                        TreeNode<K,V> r, p;
                        try {
                            p = ((r = root) == null ? null :
                                 r.findTreeNode(h, k, null));
                        } finally {
                            Thread w;
                            if (U.getAndAddInt(this, LOCKSTATE, -READER) ==
                                (READER|WAITER) && (w = waiter) != null)
                                LockSupport.unpark(w);
                        }
                        return p;
                    }
                }
            }
            return null;
}

 

Java 中,`k -> new ConcurrentHashMap<>()` 是一个 **Lambda 表达式**,通常用作函数式接口的实现,特别是在使用 `ConcurrentMap.computeIfAbsent` 或 `Map.merge` 等方法时。它表示:给定一个键 `k`,返回一个新的 `ConcurrentHashMap` 实例。 过,这种写法在实际中有些微妙之处需要理解清楚。 --- ### ✅ 正确理解与使用场景 这个 Lambda 常见于并发编程中,例如: ```java ConcurrentHashMap<String, Map<String, Object>> outerMap = new ConcurrentHashMap<>(); // 当某个 key 存在时,自动创建一个 ConcurrentHashMap 作为其值 Map<String, Object> innerMap = outerMap.computeIfAbsent("key1", k -> new ConcurrentHashMap<>()); ``` #### 解释: - `outerMap` 是一个线程安全的外层 map。 - `computeIfAbsent(key, mappingFunction)`: - 如果 `key` 对应的 value 存在,则调用传入的函数(这里是 `k -> new ConcurrentHashMap<>()`)生成 value 并放入 map。 - 如果多个线程同时访问同一个 key,`ConcurrentHashMap` 会保证该函数最多被调用一次(线程安全)。 --- ### 🔍 为什么是 `k -> new ConcurrentHashMap<>()` 而是直接 `new ConcurrentHashMap<>()`? 因为 `computeIfAbsent` 的第二个参数是一个 `Function<? super K, ? extends V>` 类型的函数接口,它接受 key 作为输入并返回 value。即使你需要使用 `k`(键),也必须提供一个接受参数的函数。 所以虽然 `k` 没有被使用,语法上仍需写成: ```java k -> new ConcurrentHashMap<>() ``` 而是: ```java () -> new ConcurrentHashMap<>() // ❌ 这是 Supplier,类型匹配 ``` --- ### ✅ 完整示例代码 ```java import java.util.concurrent.ConcurrentHashMap; import java.util.Map; public class NestedConcurrentHashMapExample { public static void main(String[] args) { // 外层 map:key 是字符串,value 是另一个并发 map ConcurrentHashMap<String, Map<String, Integer>> userScores = new ConcurrentHashMap<>(); // 获取或创建内层 map Map<String, Integer> scores = userScores.computeIfAbsent("Alice", k -> new ConcurrentHashMap<>()); // 向内层 map 添加数据 scores.put("math", 95); scores.put("english", 87); System.out.println(userScores); // 输出: {Alice={math=95, english=87}} } } ``` --- ### ⚠️ 注意事项 1. **内存开销**:每个 key 都创建一个新 `ConcurrentHashMap`,如果 key 很多,可能造成内存浪费。 2. **可变性需求?**:如果内层 map 需要修改,可以考虑使用 `Collections.emptyMap()` + `putIfAbsent` 或后续包装为可变视图。 3. **性能优化**:某些场景下可以用 `compute`, `merge` 等更高级的操作替代。 --- ### 相关技巧扩展 如果你想避免每次都新建对象(比如默认值是空集合),也可以缓存一个空的模板(但要注意是否可变): ```java private static final Function<String, Map<String, Integer>> MAP_SUPPLIER = k -> new ConcurrentHashMap<>(); ``` 然后复用: ```java userScores.computeIfAbsent("Bob", MAP_SUPPLIER); ``` --- ### 总结 `k -> new ConcurrentHashMap<>()` 是一种惯用法,用于在 `computeIfAbsent` 等方法中按需创建嵌套的并发容器。它是线程安全、延迟初始化的关键手段。 ---
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

进击的Coder*

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值