版本JDK 1.7
ConcurrentHashMap的数据结构视图
数据结构图解析
上面就是ConcurrentHashMap的数据结构图,对上面的数据结构进行解析,包含的主要元素:
- Segment
- table
- HashEntry
Segment
static final class Segment<K,V> extends ReentrantLock implements Serializable {
private static final long serialVersionUID = 2249069246763182397L;
static final int MAX_SCAN_RETRIES =
Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;
transient volatile HashEntry<K,V>[] table;
transient int count;
transient int modCount;
transient int threshold;
final float loadFactor;
Segment(float lf, int threshold, HashEntry<K,V>[] tab) {
this.loadFactor = lf;
this.threshold = threshold;
this.table = tab;
}
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
}
private void rehash(HashEntry<K,V> node) {
}
/**
* Remove; match on key only if value null, else match both.
*/
final V remove(Object key, int hash, Object value) {
}
final boolean replace(K key, int hash, V oldValue, V newValue) {
}
final V replace(K key, int hash, V value) {
}
}
Segment是ConcurrentHashMap的内部类,同时从上面代码片段中可以看到Segment也是个静态类,同时还继承了ReentrantLock,
说明Segment其实就是一个可重入锁,然后从上面的结构图中可以看到,ConcurrentHashMap里面是一个Segment数组,
然后看了上面的Segment类的主要功能之后,你会发现Segment里面拥有自己HashEntry类型的Table数组,甚至还有threshold和loadfactor
等等,所以你会发现这个Segment其实就要相当于带了锁的HashMap,然后一个Segment数组就相当于是多个带锁的HashMap,
这样如果我们将不同的就相当于将我们的K-V键值对放到了不同的HashMap中,而每个HashMap又带锁。这样就能保证部分K-V在不同的
HashMap中是线程安全的了。
对比我们之前将所有的K-V放到一个HashMap中,这样如果一个线程获得了我们整个HashMap的锁,别的线程就不能操做任何其他的K-V了,
这样锁的粒度比较大,并发性比较低。这里的实现方式就是HashTable的原理(给HashMap加了Synchronized关键字)。
现在ConcurrentHashMap用Segment数组做数据结构, 相当于拥有多个带了锁的HashMap(),然后将原本放到一个HashMap中的数据,
散射到不同的HashMap中,这样当一个线程获取到一个HashMap的锁的时候只会锁定这个HashMap中的数据,
别的线程就可以操作别的HashMap中的数据。
这就是分段锁的原理,
原理很简单:就是我们将原本放到一个HashMap中的键值对,现在分开放到不同的HashMap中,不让鸡蛋放到一个篮子里,
然后给每个HashMap都加锁,这样就可以给多个线程操作了,其实从这里我们可以明白,并发度最高的时候其实就是和我们的HashMap数量有关,
即一个线程操作一个HashMap,
最麻烦的就是我们要封装一下,主要解决我们在put,delete和get的时候映射到哪个HashMap中去做,我们自己做起来可能比较麻烦,
ConcurrentHashMap的主要工作就是做了这些操作。具体的细节可以看源代码,有了这么一个大的框架做背景,
再去看具体细节实现的时候就很简单了。
static final class HashEntry<K,V> {
final int hash;
final K key;
volatile V value;
volatile HashEntry<K,V> next;
}
另外还要注意一点HashMap的链表添加的时候是往链表尾部追加的,但是ConcurrentHashMap中的Segment中的table中链表
在追加新节点HashEntry的时候都是追加在链表的头部,如上图中虚线红色HashEntry所示。
以上就是ConcurrentHashMap在jdk1.7里面的数据架构解读。