ConcurrentMap 接口下有两个重要的实现ConcurrentHashMap、ConcurrentSkipListMap
ConcurrentSkipListMap这是一个支持高并发度的有序哈希表,并且是无锁的,可在多线程环境下替代TreeMap。支持并发排序功能,弥补ConcurrentHashMap
JDK1.7中的ConcurrentHashMap | |
底层数据结构 | 分段的数组(Segment)+链表实现 |
ConcurrentHashMap 内部使用段(Segment)来表示这些不同的部分,每一个段其实就是一个小的HashTable, 它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。把一个整体分成了16 个段(Segment)。 | |
也就是最高支持16个线程的并发修改操作。 这也是在多线程场景时减小锁粒度从而降低锁竞争的一种方案。并且代码中大多共享变量使用 volatile 关键字声明,目的是第一时间获取修改的内容,性能非常好。 | |
分段锁的思想 | 底层使用的是ReentrantLock,每把锁只锁数组中的一段数据,大大减少锁的竞争 |
Segment数组中,一个Segment对象就是一把锁,一个Segment对象对应一个HashEntry数组,通过hash值定位HashEntry数组下标 | |
HashEntry中的数据同步依赖于同一把锁,不同HashEntry数组的读写互不干扰。 | |
缺点 | 每次通过hash确认位置的时候,首先需要确认它落在哪个Segment分段,然后在这个分段里再次确认它落在哪个桶里 |
![]() | |
JDK1.8中ConcurrentHashMap | |
底层数据结构 | 在JDK1.8中,放弃了Segment臃肿的设计,数据结构跟HashMap的数据结构是一样的:数组+红黑树+链表 |
采用 CAS + Synchronized来保证并发安全进行实现 | CAS控制数组节点的添加;synchronized只锁定当前链表或红黑二叉树的首节点,只要hash不冲突,就不会产生并发的问题 , 效率得到提升。 |
常量设置 (这些设置基本和 HashMap 的类似) | ConcurrentHashMap 内部是使用一个数组存放数据的, 这个数组的长度必须是 2 的 n 次方, 默认的容量是 16, 最大是 1 << 30, 既 2 的 30 次方 |
默认负载因子为 0.75, 当数组的的存数据的项 >= 数组的长度 * 负载因子, 就会进行数组长度扩容 | |
数组的每一项, 在 JDK 1.8 中, 可以是链表, 也可以是红黑树。 | |
当数组的长度大于等于 64, 数组的某一项的长度大于等于 8, 会转为红黑树, 小于等于 6, 会重新转为链表 | |
key 和 value 都不允许为 null (HashMap 允许一个 key 为 null 和 不限制的 value 为 null) 在 ConcurrentHash 中数组中的每个节点的 hash 都有特殊作用 | |
![]() | |
总结 | 1. 底层数据结构: JDK1.7底层采用分段的数组+链表实现 JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树 |
2. 加锁的方式 JDK1.7采用Segment分段锁,底层使用的是ReentrantLock JDK1.8采用CAS添加新节点,采用synchronized锁定链表或红黑二叉树的首节点,相对Segment分段锁粒度更细,性能更好 |