ConcurrentHashMap 在 Java 中是一个线程安全的哈希表,它通过分段锁(segmentation)的方式减少锁竞争,从而允许多个线程同时写入。在 Java 8 及以后的版本中,对这个类的内部实现进行了重大改进,摒弃了原有的Segment数组,转而采用了一种分散数组+链表+红黑树的结构,并引入了CAS操作(Compare-And-Swap)、synchronized以及volatile等并发控制机制来保证线程安全。

在 JDK 1.8 及之后的 ConcurrentHashMap 实现中,并没有明确限制同时写入的线程数量。相反,它使用了一种更精细化的锁定策略——仅在需要时锁定某些桶(bucket)或节点。当多个线程尝试更新不同的桶时,它们可以并行地进行,不会彼此干扰。但是,如果多个线程尝试更新同一个桶内的节点,那么可能会存在一些争用,具体取决于节点的状态和正在执行的操作。

以下是简化的源码逻辑分析,基于 Java 8 及以上版本ConcurrentHashMap:

public V put(K key, V value) {    return putVal(key, value, false);}final V putVal(K key, V value, boolean onlyIfAbsent) {    if (key == null || value == null) throw new NullPointerException();    int hash = spread(key.hashCode());    int binCount = 0;    for (Node<K,V>[] tab = table;;) {        Node<K,V> f; int n, i, fh; K fk; V fv;        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)))                break;                   // no lock when adding to empty bin        }        else if ((fh = f.hash) == MOVED)            tab = helpTransfer(tab, f);        else {            V oldVal = null;            synchronized (f) {                if (tabAt(tab, i) == f) {                    if (fh >= 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) {                        ...                        // Tree bins are handled differently                    }                }            }            if (binCount != 0) {                if (binCount >= TREEIFY_THRESHOLD)                    treeifyBin(tab, i);                if (oldVal != null)                    return oldVal;                break;            }        }    }    addCount(1L, binCount);    return null;}

上面的代码展示了 put()方法的工作流程。

  • spread()方法用于将 hashCode()获得的高位参与到低位的计算中,以降低碰撞的概率。
  • casTabAt()是基于 CAS 操作的非阻塞算法,确保无其他线程写入时即刻成功。
  • synchronized(f)锁定当前桶的第一个节点,如果多个线程操作同一个桶,将会产生竞争。
总结:ConcurrentHashMap 允许多个线程并发写入,但具体的并行度取决于当前桶的争用情况。如果多个线程更新不同的桶,则可以完全并行;如果它们更新相同桶中的不同节点(在Java 8及以后),那么由于细粒度的锁定,通常它们也可以并行进行,除非这些节点都位于相同的位置,如同一链表或树的部分。因此,并没有固定的“允许多少个线程同时写入”的数字,这个数字是动态的,依赖于具体的竞争条件和桶的结构。

本篇文章来源于微信公众号: 互联网面试小帮手



微信扫描下方的二维码阅读本文

此作者没有提供个人介绍
最后更新于 2024-05-24