什么是 ConcurrentHashMap?
ConcurrentHashMap 是 Java 中的一个线程安全且高效的 HashMap 实现。平时涉及高并发如果要用 map 结构,那第一时间想到的就是它。相对于 hashmap 来说, ConcurrentHashMap 就是线程安全的 map,其中利用了锁分段的思想提高了并发度。
那么它到底是如何实现线程安全的?
JDK 1.6 版本关键要素: • segment 继承了 ReentrantLock 充当锁的角色,为每一个 segment 提供了线程安全的保障; • segment 维护了哈希散列表的若干个桶,每个桶由 HashEntry 构成的链表。
JDK1.8 后 ConcurrentHashMap 抛弃了原有的 Segment 分段锁,而采用了 CAS + synchronized 来保证并发安全性
ConcurrentHashMap 底层具体实现知道吗?实现原理是什么?
JDK1.7 首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用 锁访问其中一个段数据时,其他段的数据也能被其他线程访问。
在 JDK1.7 中,ConcurrentHashMap 采用 Segment + HashEntry 的方式进行实现, 结构如下: 一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和 HashMap 类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数 组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个 HashEntry 数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获 得对应的 Segment 的锁。
- 该类包含两个静态内部类 HashEntry 和 Segment ;前者用来封装映射表 的键值对,后者用来充当锁的角色;
- Segment 是一种可重入的锁 ReentrantLock,每个 Segment 守护一个 HashEntry 数组里得元素,当对 HashEntry 数组的数据进行修改时,必 须首先获得对应的 Segment 锁。
在 JDK1.8 中,放弃了 Segment 臃肿的设计,取而代之的是采用 Node + CAS + Synchronized 来保证并发安全进行实现,synchronized 只锁定当前链表或红黑 二叉树的首节点,这样只要 hash 不冲突,就不会产生并发,效率又提升 N 倍。
结构如下: 附加源码,有需要的可以看看 插入元素过程(建议去看看源码): 如果相应位置的 Node 还没有初始化,则调用 CAS 插入相应的数据;
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null, new Node(hash, key, value, null)))
break; // no lock when adding to empty bin
}
如果相应位置的 Node 不为空,且当前该节点不处于移动状态,则对该节点加 synchronized 锁,如果该节点的 hash 不小于 0,则遍历链表更新节点或插入新 节点;
if (fh >= 0) {
binCount = 1;
for (Node 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 pred = e;
if ((e = e.next) == null) {
pred.next = new Node(hash, key, value, null); break;
}
}
}
- 如果该节点是 TreeBin 类型的节点,说明是红黑树结构,则通过 putTreeVal 方法往红黑树中插入节点;如果 binCount 不为 0,说明 put 操作对数据产生了影响,如果当前链表的个数达到 8 个,则通过 treeifyBin 方法转化为红黑树,如果 oldVal 不为空,说明是一次更新操 作,没有对元素个数产生影响,则直接返回旧值;
- 如果插入的是一个新节点,则执行 addCount()方法尝试更新元素个数 baseCount;