currenthashmap底层原理
ConcurrentHashMap是Java中的一个线程安全的哈希表,也是Java并发编程领域中非常重要的一个类。它可以用来代替Hashtable和同步的HashMap,因为相对于这两个类,它有更好的并发性能和更低的锁竞争。
ConcurrentHashMap是如何实现线程安全的呢?
ConcurrentHashMap是通过分割数组来实现线程安全的。它将一个数组分割成多个小的数组片段(segment),每个片段都有一个独立的锁来控制对于该片段的访问。这样多个线程可以同时访问不同的片段,从而避免了像Hashtable一样锁住整个数据结构来保证线程安全的问题。
每个片段都是一个类似HashMap的结构,也即是一个桶数组(table),每个桶数组的元素都是一个链表(链式结构)。每个键值对的插入或读取都是在相应的片段上进行的,不同的片段之间是并发的。
ConcurrentHashMap在数据结构的基础上,还使用了一些工程上的优化,比如弱一致性的
迭代器、批量操作等,从而提高了效率,降低了锁竞争。
下面我们来具体探讨ConcurrentHashMap中的几个关键方法。
1. put方法分析
首先我们看一下put方法的源码:
```
public V put(K key, V value) {
Segment<K,V> s;
if (value == null)
throw new NullPointerException();
int hash = hash(key.hashCode());
int j = (hash >>> segmentShift) & segmentMask;
if ((s = (Segment<K,V>)segments[j]) == null) {
s = ensureSegment(j);
}
return s.put(key, hash, value, false);
}
```
其中的Segment代表ConcurrentHashMap中的每个小片段,segmentShift和segmentMask是用于计算片段索引的常量,segments代表所有的片段数组。
这段代码的作用是向ConcurrentHashMap中添加一个键值对,其中put方法是通过调用每个小片段的put方法来完成。先通过key来计算出哈希值hash,然后再通过hash计算出该键值对所属的片段索引j,如果该索引的小片段不存在,则新建一个小片段,并将其赋值给s。
最终是通过调用小片段的put方法将键值对添加到具体的桶中。在将键值对添加到桶中之前,先通过compareAndSwapObject方法(基于CAS操作)判断该桶是否已经有元素。如果有元素,则遍历链表来确定该键值对是否已经存在于链表中或者需要添加到链表末尾。如果没有元素,则直接将该键值对添加到桶中即可。
与put方法类似,get方法中也是首先通过哈希值计算出该键值对所属的小片段,然后在片段中根据该键值对的哈希值查包含该键值对的桶。如果桶中有相应的键值对,则返回该键值对的值,否则返回null。
ConcurrentHashMap中定义了一个专门用于遍历哈希表的迭代器ConcurrentHashMap.KeyIterator,它继承至抽象类ConcurrentHashMap.HashIterator,并实现了接口Iterator。
具体来说,该迭代器的实现方式是弱一致性的(weakly consistent)。在遍历哈希表时,在遍历完某个片段后,首先会尝试更新该片段中被删除的元素等,以确保后续的遍历可以跳过这些已经删除的元素。由于实际上是弱一致性的,因此在遍历范围内可能还是存在并发修改,但是会保证迭代器可以遍历到所有现存的元素,并且不会重复,也不会“挂起”
(ConcurrentModificationException)。
ConcurrentHashMap是通过分割数组来实现线程安全的,每个小片段都有一个独立的锁来控制对于该片段的访问,对于并发操作的优化也进行了很多,因此在高并发场景下表现出。它还提供了一些特别的方法(如迭代器和批量操作等),可以更好地支持并发操作。除了上述所提到的方法,ConcurrentHashMap还提供了一些其他的方法,用于更方便地进行并发操作。下面我们来看一下其中的几个方法。
1. replace方法
replace方法用于替换ConcurrentHashMap中指定键的值,只有在键存在的情况下,才会执行替换操作。该方法的定义如下:
```
public V replace(K key, V value) {
Segment<K,V> s;
HashEntry<K,V>[] tab;
int hash = hash(key.hashCode());
int j = (hash >>> segmentShift) & segmentMask;
if ((s = (Segment<K,V>)segments[j]) != null && (tab = s.table) != null) {
for (HashEntry<K,V> e = (HashEntry<K,V>)tab[(tab.length - 1) & hash]; e != null; e = e.next) {
K k;
if ((k = e.key) == key || (e.hash == hash && key.equals(k))) {
V oldValue = e.value;
e.value = value;
return oldValue;
}
}
}
return null;
}
```
与get和put方法类似,replace方法也是通过键计算出它所属的小片段,然后在小片段中查对应的键值对。如果到了指定的键,并且该键的值不是null,则将其值替换成新值。最后返回旧值。
```
public V remove(Object key) {
int hash = hash(key.hashCode());
return segmentFor(hash).remove(key, hash, null);
}
```
3. size方法
```
java replace方法 public int size() {
long sum = 0;
for (Segment<K,V> segment : segments) {
if (segment != null) {
sum += Count();
}
}
return sum >= Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)sum;
}
```
与其他方法不同,size方法遍历了所有的小片段,并将它们的大小相加,得到了总的键值对数目。
需要注意的是,由于小片段是通过分割数组来实现的,因此它们的大小可能并不相等,而且随着键值对的添加和移除,它们的大小也会发生变化。在计算ConcurrentHashMap的大小时,需要先逐个统计各个小片段的大小,然后再将它们加起来。
总结:
ConcurrentHashMap是Java中的一个线程安全的哈希表,通过将一个数组分割成多个小的数组片段,每个片段都有一个独立的锁来控制对于该片段的访问,从而实现了高并发下的线程安全。它还提供了一些优化和特别的方法(如弱一致性的迭代器和批量操作等),以支持并发操作。ConcurrentHashMap在性能和扩展性等方面也有非常好的表现,可以应对很多高并发场景。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论