Skip to content

Commit d0093d3

Browse files
committed
ConcurrentHashMap 完善
1 parent 891af35 commit d0093d3

1 file changed

Lines changed: 33 additions & 0 deletions

File tree

MD/ConcurrentHashMap.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,39 @@
1212
`ConcurrentHashMap` 采用了分段锁技术,其中 `Segment` 继承于 `ReentrantLock`。不会像 `HashTable` 那样不管是 `put` 还是 `get` 操作都需要做同步处理,理论上 ConcurrentHashMap 支持 `CurrencyLevel` (Segment数组数量)的线程并发。每当一个线程占用锁访问一个 `Segment` 时,不会影响到其他的 `Segment`
1313

1414
## get 方法
15+
ConcurrentHashMap 的 get 方法是非常高效的,因为整个过程都不需要加锁。
16+
17+
只需要将 `Key` 通过 `Hash` 之后定位到具体的 `Segment` ,再通过一次 `Hash` 定位到具体的元素上。由于 `HashEntry` 中的 `value` 属性是用 `volatile` 关键词修饰的,保证了内存可见性,所以每次获取时都是最新值([volatile 相关知识点](https://github.com/crossoverJie/Java-Interview/blob/master/MD/Threadcore.md#%E5%8F%AF%E8%A7%81%E6%80%A7))。
1518

1619
## put 方法
1720

21+
内部 `HashEntry` 类 :
22+
```java
23+
static final class HashEntry<K,V> {
24+
final int hash;
25+
final K key;
26+
volatile V value;
27+
volatile HashEntry<K,V> next;
28+
29+
HashEntry(int hash, K key, V value, HashEntry<K,V> next) {
30+
this.hash = hash;
31+
this.key = key;
32+
this.value = value;
33+
this.next = next;
34+
}
35+
}
36+
```
37+
38+
虽然 HashEntry 中的 value 是用 volatile 关键词修饰的,但是并不能保证并发的原子性。所以 put 操作时仍然需要加锁处理。
39+
40+
首先也是通过 Key 的 Hash 定位到具体的 Segment,在 put 之前会进行一次扩容校验。这里比 HashMap 要好的一点是:HashMap 是插入元素之后再看是否需要扩容,有可能扩容之后后续就没有插入就浪费了本次扩容(扩容非常消耗性能)。
41+
42+
而 ConcurrentHashMap 不一样,它是先将数据插入之后再检查是否需要扩容,之后再做插入。
43+
44+
## size 方法
45+
46+
每个 `Segment` 都有一个 `volatile` 修饰的全局变量 `count` ,求整个 `ConcurrentHashMap``size` 时很明显就是将所有的 `count` 累加即可。但是 `volatile` 修饰的变量却不能保证多线程的原子性,所有直接累加很容易出现并发问题。
47+
48+
但如果每次调用 `size` 方法将其余的修改操作加锁效率也很低。所以做法是先尝试两次将 `count` 累加,如果容器的 `count` 发生了变化再加锁来统计 `size`
49+
50+
至于 `ConcurrentHashMap` 是如何知道在统计时大小发生了变化呢,每个 `Segment` 都有一个 `modCount` 变量,每当进行一次 `put remove` 等操作,`modCount` 将会 +1。只要 `modCount` 发生了变化就认为容器的大小也在发生变化。

0 commit comments

Comments
 (0)