文章目录
一、HashTable
HashTable是线程安全的HashMap,里面的方法都是用synchronized修饰过的,跟HashMap有以下不同之处:
- 初始容量不同,HashMap是16,HashTable是11
- HashMap可以存储值为null的元素,而HashTable不可以
- HashMap的迭代器是基于快速失败(modCount)机制,fail-fast,而HashTable的迭代器不是快速失败的。
二、Colletions.synchronizedMap
除HashTable外,利用Colletions.synchronizedMap也可以得到线程安全的map,synchronizedMap是Collections的一个静态内部类,里面的方法都是用synchronized代码块去修饰的,因此也是线程安全的。
三、1.7的ConcurrentHashMap
在JDK 1.7的ConcurrentHashMap,底层是segement数组+HashEntry数组+链表这样的数据结构。
第一层就是segement数组,每一个segement里都有一个HashEntry数组,ConcurrentHashMap一旦初始化之后,segement数组默认长度是16,也就是所能支持的并发程度是16,但是segement数组是不能动态扩容的,而每一个segement里面的HashEntry是可以动态扩容的。
1.7的ConcurrentHashMap基于segement分段锁这样的机制,segement是继承了ReentranLock,通过ReentranLock来进行一个并发控制,操作数据时,会首先判断当前的key属于哪个segement,拿到当前的segement锁之后才能进行操作,写操作不用获取segement锁,因为value是用valotile修饰的。
1.7 初始化源码解析
如果使用空的构造方法,则会赋予三个默认值,初始容量(16)、负载因子(0.75)、并发程度(16)。最后使用默认构造函数执行有参构造方法。
public ConcurrentHashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
}
@SuppressWarnings("unchecked")
public ConcurrentHashMap(int initialCapacity,float loadFactor, int concurrencyLevel) {
... //上面都是一些合法性校验
//Segment 中的类似于 HashMap 的容量至少是2或者2的倍数
while (cap < c)
cap <<= 1;
// create segments and segments[0]
// 创建 Segment 数组,设置 segments[0]
Segment<K,V> s0 = new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
(HashEntry<K,V>[])new HashEntry[cap]);
Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];
UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
this.segments = ss;
}
传入默认参数执行有参构造方法,前面都是一些参数的合法性校验,最重要的下面几句。
以默认的capacity创建Segement数组,然后创建第0号位置的segement,第0号位置的segement里的HashEntry数组大小为2,负载因子是0.75,也就是说往第0号位置的segement put第二个元素时,第0号位置的segement的HashEntry数组才会进行扩容,segement数组不会扩容。
为什么只创建第0号位置的segement?因为在put的时候是以此为原型的,可以看put方法。
1.7 put
public V put(K key, V value) {
Segment<K,V> s;
if (value == null)
throw new NullPointerException();
int hash = hash(key);
// hash 值无符号右移 28位(初始化时获得),然后与 segmentMask=15 做与运算
// 其实也就是把高4位与segmentMask(1111)做与运算
int j = (hash >>> segmentShift) & segmentMask;
if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck
(segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment
// 如果查找到的 Segment 为空,初始化
s = ensureSegment(j);
return s.put(key, hash, value, false);
}
进入put方法,会首先获取到key的hash值,通过右移偏移量和segementMask进行与运算得到当前key属于哪个segement,如果当前segement为空则进行segement的初始化–ensureSegement方法。
ensureSegement
private Segment<K,V> ensureSegment(int k) {
final Segment<K,V>[] ss = this.segments;
long u = (k << SSHIFT) + SBASE; // raw offset
Segment<K,V> seg;
// 判断 u 位置的 Segment 是否为null
if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {
Segment<K,V> proto = ss[0]; // use segment 0 as prototype
// 获取0号 segment 里的 HashEntry<K,V> 初始化长度
int cap = proto.table.length;
// 获取0号 segment 里的 hash 表里的扩容负载因子,所有的 segment 的 loadFactor 是相同的
float lf = proto.loadFactor;
// 计算扩容阀值
int threshold = (int)(cap * lf);
// 创建一个 cap 容量的 HashEntry 数组
HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry[cap];
if ((seg = (Segment