CurrentHashMap
2020年12月28日
15:03
目录 |
- - 1. 引言 - 2. 分段锁数据结构 - 3. JDK1.7实现 - 3.1 初始化 - 3.2 插入 - 4. JDK1.8实现 - 4.1 初始化 - 4.2 插入 - 5. REF
|
1. 引言
CurrentHashMap(CHM)是并发容器的代表,是一种实现细粒度锁提高并发性能的容器。
它的核心思想是分段锁,即缩小每次锁的数据的数量,从而提高并发性能。
话又说回来,我回想锁一大堆机制,其实本质上应该不是一件复杂的事。类比一下,这件事类似于几人搬箱子(假设一个人只能搬一个),提高效率的方式:
- 要么你把箱子拆成多份(细粒度的锁)
- 要么一个人搬,其他人就在旁边等等,不要休息(因为休息阻塞会花费大量的时间,乐观锁就是基于自旋等待,假设自旋很快)
CurrentHashMap在jdk1.7和1.8之间发生了较大的变化,但事实上和HashMap的实现很像。JDK1.7引入了segment的二阶段数据结构,每次只对segment加锁,JDK1.8则和hashMap非常像,只是它加锁只加hash值指向的节点。
2. 分段锁数据结构
这是CHM的灵魂,不过下图其实是JDK1.7的实现,而JDK1.8并没有这种显示的两阶段数据结构,但是思想是一样的。
3. JDK1.7实现
3.1 初始化
- 1.7 是创建立马初始化,1.8是put的时候进行初始化
- 初始化主要是初始segment表和一些计算hash用的标志,掩码等
1 public ConcurrentHashMap(int initialCapacity,float loadFactor, int concurrencyLevel) { 2 // 参数校验 3 if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0) 4 throw new IllegalArgumentException(); 5 // 校验并发级别大小,大于 1<<16,重置为 65536 6 if (concurrencyLevel > MAX_SEGMENTS) 7 concurrencyLevel = MAX_SEGMENTS; 8 // Find power-of-two sizes best matching arguments 9 // 2的多少次方 10 int sshift = 0; 11 int ssize = 1; 12 // 这个循环可以找到 concurrencyLevel 之上最近的 2的次方值 13 while (ssize < concurrencyLevel) { 14 ++sshift; 15 ssize <<= 1; 16 } 17 // 记录段偏移量,计算hash,mod用 18 this.segmentShift = 32 - sshift; 19 // 记录段掩码 20 this.segmentMask = ssize - 1; 21 // 设置容量 22 if (initialCapacity > MAXIMUM_CAPACITY) 23 initialCapacity = MAXIMUM_CAPACITY; 24 // c = 容量 / ssize ,默认 16 / 16 = 1,这里是计算每个 Segment 中的类似于 HashMap 的容量 25 int c = initialCapacity / ssize; 26 if (c * ssize < initialCapacity) 27 ++c; 28 int cap = MIN_SEGMENT_TABLE_CAPACITY; 29 //Segment 中的类似于 HashMap 的容量至少是2或者2的倍数 30 while (cap < c) 31 cap <<= 1; 32 // create segments and segments[0] 33 // 创建 Segment 数组,设置 segments[0] 34 Segment<K,V> s0 = new Segment<K,V>(loadFactor, (int)(cap * loadFactor), 35 (HashEntry<K,V>[])new HashEntry[cap]); 36 Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize]; 37 UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0] 38 this.segments = ss; 39 } |
3.2 插入
对于整个map的插入 由于是分段的数据结构,逻辑就是
- 获得分段位置
- 往分段位置的hash表中插入
1 public V put(K key, V value) { 2 Segment<K,V> |