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)锁定当前桶的第一个节点,如果多个线程操作同一个桶,将会产生竞争。
本篇文章来源于微信公众号: 互联网面试小帮手
微信扫描下方的二维码阅读本文

Comments NOTHING