浅谈 ConcurrentHashMap,2021 大厂 Android 面试题精选
ConcurrentHashMap
?是线程安全且高效的 HashMap。
不同版本 jdk 实现它的方式存在些许不同,本篇文章主要基于 JDK1.8 进行分析。
1 为什么使用 ConcurrentHashMa
p
HashMap
是线程不安全的。在多线程环境下,使用 HashMap 进行 put 操作会引起死循环。HashTable
的效率低下,因为它几乎在所有方法上都使用 synchronized 来保证线程安全。ConcurrentHashMap
在 put、remove 等修改数据操作时,只锁住某个“桶”的方式,有效提升并发访问率。
2 主要方法
2.1 put()
先上波源码再来分析。
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;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) {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)treeifyBin(tab, i);if (oldVal != null)return oldVal;break;}}}addCount(1L, binCount);return null;}
第一步做了一个校验,key 或 value 为 null 都会抛出异常。 然后获取 hash 值,初始化 binCount 用于记录链表长度。
这里是个无限循环。
先判断 table 有没有初始化,没有就进行初始化。
(n - 1) & hash
计算出插入下标,如果这个位置没有数据,直接放进去。
(fh = f.hash) == MOVED
如果其他线程在扩容,帮助其扩容。
// 有冲突后,进去 elsesynchronized (f) { // 锁住当前头节点 if (tabAt(tab, i) == f) { // 再次确认下标位置是否是 f 节点 if (fh >= 0) { // 头节点 hash 值 > 0 说明是链表 binCount = 1;for (Node<K,V> e = f;; ++binCount) { // 遍历 K ek;// 如果 key 相同,则判断是否覆盖旧值 if (e.hash == hash &&
评论