1.7
分段锁 segment
数组 Segment + 数组 HashEntry + 链表 HashEntry结点
对数组进行分割,锁住部分数据
ReentrantLock lock-> Segment
1.8
static final int MOVED = -1; // hash for forwarding nodes
static final int TREEBIN = -2; // hash for roots of trees
static final int RESERVED = -3; // hash for transient reservations
Node数组 + 链表/红黑树
基本锁原理
CAS + synchronized -> Node
get方法没有加锁 node 里面的 next 和 value 加了 volatile关键字
put方法
计算key的hash值,计算出在数组table的下标i
1. table[i]==null (数组目标位置的node为空)
说明之前这个位置没有元素,可以直接插入,chm采用的是cas比较替换的方法插入
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
Node<K,V> c, Node<K,V> v) {
return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
ASHIFT :每个node的大小
ABASE :数组开始存值的偏移地址
2. firstNode.hash == MOVED(-1)
表示正在扩容中,可以帮助多线程扩容
tab = helpTransfer(tab, firstNode);
-->
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
3. 比较插入
synchronized(firstNode)
锁住数组此位置上的第一个结点
如果是链表结点,比较hash and key.equals()
如果是树结点( firstNode.hash == TREEBIN(-2) ), 用红黑树的方法查找和插入,再平衡
resize() core
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
setTabAt(nextTab, i, ln);
setTabAt(nextTab, i + n, hn);
setTabAt(tab, i, fwd); //在旧的table的firstNode设置ForwardingNode 别的线程如果查询时这个结点则帮助扩容
advance = true; //处理下一个结点扩容
//扩容完成的操作
nextTable = null;
table = nextTab;
for循环table的结点,一个个位置扩容直到全部扩容完成
扩容的时间锁住firstNode synchronized(firstNode)
如果扩容完成则加一个标记 table[i]=ForwardingNode
get()方法
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null;
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
1. table[i] == key
firstNode为查找的目标,直接返回value
2. table[i].hash == -1
说明老table对应的结点已经扩容完成,在ForwardingNode可以用nextTable
去查找
3. table[i].hash == -2
说明table对应的结点是树结点,用树结点的方式去查找
4. 链表查找
遍历链表查找
优点
- 锁粒度降低
- 无锁操作,get无锁,volatile特性
- 乐观锁替换,cas替换第一个元素
- 并发扩容
- 使用优化后的synchronized关键字,性能也高