Java 深入理解 ConcurrentHashMap (数据结构、实现原理、JDK版本区别,读写相关锁)
问题:JDK1.7和JDK1.8的ConcurrentHashMap底层数据结构有何区别?JDK1.7和JDK1.8的ConcurrentHashMap实现原理有何区别?HashMap、ConcurrentHashMap主要有何区别?ConcurrentHashMap初始大小、加载因子分别是多少?ConcurrentHashMap的put方法的主要流程?在JDK1.8中ConcurrentHash
问题:
- JDK1.7和JDK1.8的ConcurrentHashMap底层数据结构有何区别?
- JDK1.7和JDK1.8的ConcurrentHashMap实现原理有何区别?
- HashMap、ConcurrentHashMap主要有何区别?
- ConcurrentHashMap初始大小、加载因子分别是多少?
- ConcurrentHashMap的put方法的主要流程?
- 在JDK1.8中ConcurrentHashMap,为何要用synchronized代替ReentrantLock?
- ConcurrentHashMap和HashTable有何区别?
- 为什么ConcurrentHashMap比HashTable更高效?
- ConcurrentHashMap的get方法的主要流程?
- ConcurrentHashMap读写需要加锁么?为何读不用加锁?
- ConcurrentHashMap迭代器是强一致性还是弱一致性?HashMap呢?
文章目录
-
- 1. JDK1.7和JDK1.8的ConcurrentHashMap底层数据结构有何区别?
- 2. JDK1.7和JDK1.8的ConcurrentHashMap实现原理有何区别?
- 3. HashMap、ConcurrentHashMap主要有何区别?
- 4. ConcurrentHashMap初始大小、加载因子分别是多少?
- 5. ConcurrentHashMap的put方法的主要流程?
- 6. 在JDK1.8中ConcurrentHashMap,为何要用synchronized代替ReentrantLock?
- 7. ConcurrentHashMap和HashTable有何区别?
- 8. 为什么ConcurrentHashMap比HashTable更高效?
- 9. ConcurrentHashMap的get方法的主要流程?
- 10. ConcurrentHashMap读写需要加锁么?为何读不用加锁?
- 11. ConcurrentHashMap迭代器是强一致性还是弱一致性?HashMap呢?
1. JDK1.7和JDK1.8的ConcurrentHashMap底层数据结构有何区别?
- 1.7 版本ConcurrentHashMap 是由一个 Segment 数组和多个 HashEntry 组成
- Segment 数组的意义就是将一个大的 table 分割成多个小的 table 来进行加锁(分段锁技术),而每一个 Segment 元素存储的是 HashEntry 数组。
- JDK1.8 的实现废弃了 Segment,使用 Node数组+链表+红黑树的数据结构来实现。在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容版本。
JDK1.7:
JDK1.8: (规则和HashMap差不多)
2. JDK1.7和JDK1.8的ConcurrentHashMap实现原理有何区别?
-
数据结构不同
-
线程安全机制不同:
JDK1.7是 ReentrantLock, JDK1.8是 CAS + synchronized
-
锁的粒度
JDK1.7是对需要进行数据操作的 Segment 加锁,1.8 是对每个数组元素加锁(Node)
3. HashMap、ConcurrentHashMap主要有何区别?
- ConcurrentHashMap 是线程安全的,HashMap 不是线程安全的
- 除了加锁,原理上没有太大区别。另外,HashMap 的键值对允许有null,但是 ConcurrentHashMap 都不允许。
4. ConcurrentHashMap初始大小、加载因子分别是多少?
ConcurrentHashMap 和 HashMap 的初始大小都是 16,加载因子都是 0.75.
ThreadLocalMap 初始大小为 16,加载因子为 2/3.
加载因子:是衡量哈希表密集程度的一个参数,加载因子与hash冲突(过高不良)的可能性成正相关,与装载量成正相关,与内存使用率(过低不良)成正相关。该值取值应该考虑到内存使用率和hash冲突概率的平衡。
5. ConcurrentHashMap的put方法的主要流程?
-
如果没有初始化就先调用initTable方法来进行初始化过程
-
如果没有hash冲突就直接 CAS 插入
-
如果还在进行扩容操作就先进行扩容
-
如果有 hash 冲突,则加锁来保证线程安全。有两种情况;1、链表形式就直接遍历到尾端插入(尾插法),2、红黑树就按照红黑树结构插入
先插入,然后再判断是否转为红黑树。
-
如果链表数量大于阈值 8,且数组长度大于 64,则转换为红黑树
先插入,再(可能)转为红黑树,然后再判断是否扩容。
-
如果添加成功就调用 addCount方法 统计 size,并检查是否需要扩容(在 transfer方法 中真正扩容,会有一个newtable)。
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;;) {//循环是为了CAS+自旋
Node<K,V> f; int n, i, fh;
//如果没有初始化就先调用initTable方法来进行初始化过程
if (tab == null || (n = tab.length) == 0)
tab = initTable();
//如果没有hash冲突就直接 CAS 插入
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);
//如果不在扩容操作,就锁定Node并做添加操作
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;
}
}
}
}
//如果链表数量大于阈值 8,且数组长度大于 64,则转换为红黑树
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
//如果添加成功就调用 addCount方法统计size,并检查是否需要扩容
addCount(1L, binCount);
return null;
}
6. 在JDK1.8中ConcurrentHashMap,为何要用synchronized代替ReentrantLock?
-
减少内存开销
使用 ReentrantLock 则需要节点继承 AQS 来获得同步支持,增加内存开销,而1.8中只有头结点需要进行同步,(后续的节点是可以不用锁的,也就不需要全都继承 AQS 来获取同步支持)。
-
内部优化
synchronized 则是 JVM 直接支持的,JVM 能够在运行时做出相应的优化措施:锁粗化、锁消除、锁自旋等等。(见锁优化)
7. ConcurrentHashMap和HashTable有何区别?
相同点:
- Hashtable 和 ConcurrentHashMap 都是线程安全的,可以在多线程环境中运行, key 和 value 都不能是 null;
不同点:
- Hashtable 使用一把锁(锁住整个链表结构,synchronized 修饰了 put 方法)处理并发问题,多线程竞争一把锁,容易阻塞。
- JDK1.8中,ConcurrentHashMap 使用 CAS+非公平自旋 + synchronized(Node) + Node + 红黑树。锁粒度: Node(首节点),锁粒度降低了。
- ConcurrentHashMap 的性能要比 Hashtable 要好。
8. 为什么ConcurrentHashMap比HashTable更高效?
ConcurrentHashMap 使用 CAS+非公平自旋 + synchronized(Node) + volatile + Node + 红黑树。锁粒度: Node(首节点),锁粒度降低了。
Hashtable 使用一把锁(锁住整个链表结构,synchronized 修饰了 put 方法)处理并发问题,多线程竞争一把锁,容易阻塞。
9. ConcurrentHashMap的get方法的主要流程?
- 计算 hash 值,定位到该 table 索引位置,如果是首节点符合就返回
- 如果遇到扩容的时候,会调用标志正在扩容节点 ForwardingNode 的 find 方法,查找该节点,匹配就返回
- 以上都不符合的话(又不是首节点,也不再扩容,说明要么在链表中,要么在红黑树中),就往下遍历节点,匹配就返回,否则最后就返回 null
get 的时候不需要加锁,因为 Node 变量是被 volatile 修饰的,有可见性。只要写操作保证了线程安全,读线程就能安全。
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
int h = spread(key.hashCode());
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
if ((eh = e.hash) == h) {
//计算 hash 值,定位到该 table 索引位置,如果是首节点符合就返回
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
else if (eh < 0)
//如果遇到扩容的时候,会调用标志正在扩容节点 ForwardingNode 的 find 方法,查找该节点,匹配就返回
return (p = e.find(h, key)) != null ? p.val : null;
//以上都不符合的话,就往下遍历节点,匹配就返回,否则最后就返回 null
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
10. ConcurrentHashMap读写需要加锁么?为何读不用加锁?
写加锁,读不用加锁.
Node 元素 val 和指针 next 是用 volatile 修饰的,在多线程环境下线程A修改因为 hash 冲突修改节点的 val 或者新增节点,对其他线程都是及时可见的(见 volatile 如何保证原子性:volatile 与 内存屏障
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
volatile V val;
volatile Node<K,V> next;
...
}
11. ConcurrentHashMap迭代器是强一致性还是弱一致性?HashMap呢?
ConcurrentHashMap 迭代器是弱一致性,HashMap 强一致性。
ConcurrentHashMap 可以支持在迭代过程中,向 map 添加新的元素,而 HashMap 则会抛出 ConcurrentModificationException,因为 HashMap 包含一个修改计数器,当调用 HashMap 的 next() 方法获取下一个元素的时候,迭代器将会用到这个计数器。

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐
所有评论(0)