本文目录索引:
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 。 输入参数 initialCapacity 是 ConcurrentHashMap 的初始化容量, loadfactor 是 每个 segment 的负载因子,在构造方法里需要通过这两个参数来初始化数组中的 每个 segment 。上面代码中的变量 cap 就是 segment 里 HashEntry 数组的长度, 它等于 initialCapacity 除以 ssize 的倍数 c ,如果 c 大于 1 ,就会取大于等于 c 的 2 的 N 次方值,所以 segment 里 HashEntry 数组的长度不是 1 ,就是 2 的 N 次方。 在默认情况下, ssize = 16 , initialCapacity = 16 , loadFactor = 0.75f ,那么 cap = 1 , threshold = (int) cap * loadFactor = 0 。 既然 ConcurrentHashMap 使用分段锁 Segment 来保护不同段的数据,那么在 插入和获取元素的时候,必须先通过散列算法定位到 Segment 。 ConcurrentHashMap 会首先使用 Wang/Jenkins hash 的变种算法对元素的 hashCode 进行一次再散列。 ConcurrentHashMap 完全允许多个读操作并发进行,读操作并不需要加锁。 ConcurrentHashMap 实现技术是保证 HashEntry 几乎是不可变的以及 volatile 关键 字。