简介
与ConcurrentHashMap类似的Java Conllections还有Hashtable和HashMap,HashMap不是一个线程安全的类,key和value的值都可以是null,ConcurrentHashMap和HashMap都是线程安全的类,但是key和value的值都不能是null。Hashtable保证线程安全的做法是每个可调用的方法都是synchronized的。这意味着每个线程访问Hashtable时都会把整个Hashtable锁起来。
> HashMap和HashTable的结构图:
根据上图可以看出同步的Hash容器是同步使用锁来保证的,并且所有同步操作使用的是同一个锁对象。这样若有n个线程同时在get时,这n个线程要串行的等待来获取锁。(效率很低!)
ConcurrentHashMap则采用了分段锁(Segment)的机制,使得竞态条件只发生在Segment内,增加了并发度;同时,在HashMap.Entry的基础上进行改进,ConcurrentHashMap.Entry的value和next域都是volatile的,可以保证在多线程环境下对于其他线程的可见性。由于ConcurrentHashMap不是对整个表加锁,在执行size()这些全局性质的方法时需要遍历整个Segment数组。
ConcurrentHashMap 的结构分析
为了更好的理解 ConcurrentHashMap 高并发的具体实现,让我们先探索它的结构模型。
ConcurrentHashMap 类中包含两个静态内部类 HashEntry 和 Segment。HashEntry 用来封装映射表的键 / 值对;Segment 用来充当锁的角色,每个 Segment 对象守护整个散列映射表的若干个桶。每个桶是由若干个 HashEntry 对象链接起来的链表。一个 ConcurrentHashMap 实例中包含由若干个 Segment 对象组成的数组。
HashEntry 类
HashEntry 用来封装散列映射表中的键值对,在 HashEntry 类中,key,hash 和 next 域都被声明为 final 型,value 域被声明为 volatile 型。
<pre><code>static final class HashEntry<K, V> {
final int hash;
final K key;
volatile V value;
volatile HashEntry<K, V> next;
}</code></pre>
Segment 类
Segment 类继承于 ReentrantLock 类,从而使得 Segment 对象能充当锁的角色。每个 Segment 对象用来守护其(成员对象 table 中)包含的若干个桶。
table 是一个由 HashEntry 对象组成的数组。table 数组的每一个数组成员就是散列映射表的一个桶。
count 变量是一个计数器,它表示每个 Segment 对象管理的 table 数组(若干个 HashEntry 组成的链表)包含的 HashEntry 对象的个数。每一个 Segment 对象都有一个 count 对象来表示本 Segment 中包含的 HashEntry 对象的总数。注意,之所以在每个 Segment 对象中包含一个计数器,而不是在 ConcurrentHashMap 中使用全局的计数器,是为了避免出现“热点域”而影响 ConcurrentHashMap 的并发性。
Segment 类的定义:
<pre><code>static final class Segment<K, V> extends ReentrantLock implements Serializable {
//在本 segment 范围内,包含的 HashEntry 元素的个数
transient volatile int count;
//table 被更新的次数
transient int modCount;
//当 table 中包含的 HashEntry 元素的个数超过本变量值时,触发 table 的再散列resize
transient int threshold;
//装载因子
final float loadFactor;
//table 是由 HashEntry 对象组成的数组
transient volatile HashEntry<K,V>[] table;
}
</code></pre>