1. Key == null ? Value == null?
记忆:线程安全的Map:HashTable,concurrentHashMap,键值都不允许为null,hashMap不安全,键值可以为null。
2. 如果需要使用自定义对象做为 Map 集合的 key,那么一定记得重写hashCode 与 equals 方法
所以尽量避免使用自定义对象作为 Map 集合 key,如果一定要使用,记得重写 hashCode 与 equals 方法。另外还要保证这是一个不可变对象,即对象创建之后,无法再修改里面字段值。
1.7的ConcurrentHashMap
-
使用分段锁的方式,是Segment+HashEntry的组合,HashEntry又是一个链表数组,所以1.7的conHashMap相当于是多个HashTable的组合,使用多个HashTable来提升并发性。
-
在高并发下的情况下如何保证取得的元素是最新的:用于存储键值对数据的HashEntry,在设计上它的成员变量value跟next都是volatile类型的,这样就保证别的线程对value值的修改,get方法可以马上看到。
-
这个size方法比较有趣,他是先无锁的统计下所有的数据量看下前后两次是否数据一样,如果一样则返回数据,如果不一样则要把全部的segment进行加锁,统计,解锁。并且size方法只是返回一个统计性的数字,因此size谨慎使用
1.8的ConcurrentHashMap
-
取消了segment数组,直接用Node数组保存数据,锁的粒度更小,减少并发冲突的概率。采用table数组元素作为锁,从而实现了对每一行数据进行加锁,进一步减少并发冲突的概率,并发控制使用Synchronized和CAS来操作。
-
存储数据时采用了数组+ 链表+红黑树的形式
-
链表转为树的两个条件 : 64 + 8 , 树转为链表 6
假设table已经初始化完成,put操作采用 CAS + synchronized 实现并发插入或更新操作,具体实现如下。
- 做一些边界处理,然后获得hash值。(key ,value 不能为null)
if (key null || value null) throw new NullPointerException(); //边界处理
- 没初始化就初始化,初始化后看下对应的桶是否为空,为空就原子性的尝试插入。
if (tab null || (n = tab.length) 0)
tab = initTable(); // 初始化表 如果为空,懒汉式
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
- 如果当前节点正在扩容还要去帮忙扩容,骚操作。
else if ((fh = f.hash) MOVED)
// 如果当前节点正在扩容。还要帮着去扩容。
tab = helpTransfer(tab, f);
- 用syn来加锁当前节点,然后操作几乎跟就跟hashmap一样了。
synchronized (f) // 桶存在数据 加锁操作进行处理
// 链表尾插
for (Node<K,V> e = f;; ++binCount) {
K ek;
// 遍历链表去查找,如果找到key一样则选择性
if (e.hash hash &&
((ek = e.key) key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) null) {// 找到尾部插入
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
// 红黑树插入
else if (f instanceof TreeBin) {// 如果桶节点类型为TreeBin
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
// 尝试红黑树插入,同时也要防止节点本来就有,选择性覆盖
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
- 最后插完判断是否需要转成树和扩容
if (binCount != 0) { // 如果链表数量
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i); // 链表转红黑树哦!
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount); // 统计大小 并且检查是否要扩容。
return null;