ConcurrentHashMap 原理

背景知识:我们知道 HashMap 是线程不安全的,多线程情况下,进行put 操作会引起死循环,导致CPU 利用率比较高,所以在并发情况下不能使用 HashMap ;

Hashtable 是线程安全的,其实就是在 put 操作的时候加了 syncronized, 但是当一个线程在进行put 操作时,其它的线程连 get 操作也不能进行,导致阻塞或者轮询状态,所以竞争越来越激烈,效率低下。

目录结构:

1
2
3
4
public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
implements ConcurrentMap<K,V>, Serializable {
.....
}
  • 锁分段技术

    Hashtable 在多并发的情况下效率低下,是因为很多线程争夺同一把锁,而假如容器内有多把锁,每一把锁用于锁容器内的一部分数据,当多线程访问容器内不同段的数据时,线程间就不会存在锁竞争,从而有效的提高并发访问效率。

Segment

英文翻译:” 段“

内部结构是:二级的哈希表,如下图:

image.png

多个Segment 保存在一个名为segments 数组当中,每个 Segment 高度自治,各个 Segment 之前读写互不影响。

读写操作

  • get

    想要获取里面的元素,按照结构,我们大概也能猜测出如何获取。

      1. 首先根据 key 计算的 hash 值;
      2. 计算得出 在 segments 数组中的下标,根据下标得出相对应的 Segment
      3. 再根据keyhash 值,找到Segment 数组中的位置
  • put

    put 元素

    • 1.首先根据 key 计算hash
    • 2.通过 hash 值,定位到具体的 Segment 对象
    • 3.获取可重入锁
    • 4.再次通过 hash 值,定位到 Segment 数组中的具体位置
    • 5.插入或覆盖原 HashEntry 对象
    • 6.释放锁

统计Size大小

jdk 1.7:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public int size() {
// Try a few times to get accurate count. On failure due to
// continuous async changes in table, resort to locking.
final Segment<K,V>[] segments = this.segments;
int size;
boolean overflow; // true if size overflows 32 bits
long sum; // sum of modCounts
long last = 0L; // previous sum
int retries = -1; // first iteration isn't retry
try {
for (;;) {
if (retries++ == RETRIES_BEFORE_LOCK) {
for (int j = 0; j < segments.length; ++j)
ensureSegment(j).lock(); // force creation
}
sum = 0L;
size = 0;
overflow = false;
for (int j = 0; j < segments.length; ++j) {
Segment<K,V> seg = segmentAt(segments, j);
if (seg != null) {
sum += seg.modCount;
int c = seg.count;
if (c < 0 || (size += c) < 0)
overflow = true;
}
}
if (sum == last)
break;
last = sum;
}
} finally {
if (retries > RETRIES_BEFORE_LOCK) {
for (int j = 0; j < segments.length; ++j)
segmentAt(segments, j).unlock();
}
}
return overflow ? Integer.MAX_VALUE : size;
}
    1. 遍历所有的 Segment
    2. Segment 元素数量累加起来
    3. Segment 修改次数累加起来
    4. 判断所有的Segment 的总修改次数是否大于上一次的总修改次数。如果大于,说明统计过程有修改,尝试统计,统计次数+1;反之,统计结束。
    5. 如果统计次数超过阀值,则对每个 Segment 加锁,重新统计
    6. 再次判断总修改次数是否大于上一次的总修改次数,
    7. 释放锁,统计结束。
-------------本文结束感谢您的阅读-------------