本文目录索引:
1、概要 2、 构造方法和初始化 3、 get 操作 4、put操作 5、rehash操作 6、remove操作 7、 ConcurrentHashMap 的弱一致性 8、 size 、 containsValue1、概要
ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成。
Segment 是一种可重入锁(ReentrantLock),在 ConcurrentHashMap 里扮演锁的
角色;HashEntry 则用于存储键值对数据。一个 ConcurrentHashMap 里包含一个
Segment 数组。 Segment 的结构和 HashMap 类似,是一种数组和链表结构。一个 Segment 里包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素, 每个 Segment 守护着一个 HashEntry 数组里的元素,当对 HashEntry 数组的数据 进行修改时,必须首先获得与它对应的 Segment 锁。

2、构造方法和初始化
ConcurrentHashMap 初始化方法是通过 initialCapacity、loadFactor 和
concurrencyLevel( 参数 concurrencyLevel 是用户估计的并发级别,就是说你觉得最 多有多少线程共同修改这个 map ,根据这个来确定 Segment 数组的大小 concurrencyLevel 默认是 DEFAULT_CONCURRENCY_LEVEL = 16;) 等几个参数来初始 化 segment 数组、段偏移量 segmentShift 、段掩码 segmentMask 和每个 segment 里的 HashEntry 数组来实现的。 并发级别可以理解为程序运行时能够同时更新 ConccurentHashMap 且不产 生锁竞争的最大线程数,实际上就是 ConcurrentHashMap 中的分段锁个数,即 Segment[] 的数组长度。 ConcurrentHashMap 默认的并发度为 16 ,但用户也可以 在构造函数中设置并发度。当用户设置并发度时, ConcurrentHashMap 会使用大 于等于该值的最小 2 幂指数作为实际并发度(假如用户设置并发度为 17 ,实际 并发度则为 32 )。 如果并发度设置的过小,会带来严重的锁竞争问题;如果并发度设置的过大, 原本位于同一个 Segment 内的访问会扩散到不同的 Segment 中, CPU cache 命中 率会下降,从而引起程序性能下降。(文档的说法是根据你并发的线程数量决定, 太多会导性能降低) segments 数组的长度 ssize 是通过 concurrencyLevel 计算得出的。为了能通 过按位与的散列算法来定位 segments 数组的索引,必须保证 segments 数组的长 度是 2 的 N 次方( power-of-two size ),所以必须计算出一个大于或等于 concurrencyLevel 的最小的 2 的 N 次方值来作为 segments 数组的长度。假如 concurrencyLevel 等于 14 、 15 或 16 , ssize 都会等于 16 ,即容器里锁的个数也是 16 。 初始化 segmentShift 和 segmentMask 这两个全局变量需要在定位 segment 时的散列算法里使用, sshift 等于 ssize 从 1 向左移位的次数,在默认情况下 concurrencyLevel 等于 16 , 1 需要向左移位 移动 4 次,所以 sshift 等于 4 。 segmentShift 用于定位参与散列运算的位数, segmentShift 等于 32 减 sshift ,所以等于 28 ,这里之所以用 32 是因为 ConcurrentHashMap 里的 hash() 方法输出的最大数是 32 位的。 segmentMask 是散 列运算的掩码,等于 ssize 减 1 ,即 15 ,掩码的二进制各个位的值都是 1 。因为 ssize 的最大长度是 65536 ,所以 segmentShift 最大值是 16 , segmentMask 最大值 是 65535 ,对应的二进制是 16 位,每个位都是 1 。

3、get 操作

4、put操作




5、rehash操作
扩容是新创建了数组,然后进行迁移数据,最后再将 newTable 设置给属性 table 。 为了避免让所有的节点都进行复制操作:由于扩容是基于 2 的幂指来操作, 假设扩容前某 HashEntry 对应到 Segment 中数组的 index 为 i ,数组的容量为 capacity ,那么扩容后该 HashEntry 对应到新数组中的 index 只可能为 i 或者 i+capacity ,因此很多 HashEntry 节点在扩容前后 index 可以保持不变。 假设原来 table 长度为 4 ,那么元素在 table 中的分布是这样的