链表树化:判断表长度是否小于最小64,小于64tryPresize;大于64进行树化,对桶使用synchronized加锁,一个桶同时只能由一个线程进行转换,最后将转化后的链置入TreeBin容器,构造TreeBin容器时会构造树,构造结束时检查链表和树指针是否正确。
put流程: 1.首先计算的hash一定>=0(与0x7fffffff进行&运算),特殊的节点hash小于0,-1代表已移动,-2代表树容器TreeNode(指向红黑树);
2.sizeCtl:如果为负,则表正在初始化或调整大小:-1 表示初始化,否则 -(1 + 活动调整大小线程的数量)。 初始化后,保存下一个要调整表格大小的元素计数值。
//sizeCtl 高16(RESIZE_STAMP_BITS)位是扩容标记第一位是1,所以是一个负数;低16(RESIZE_STAMP_SHIFT)位是并行线程数
3.如果是初始化table,CAS将sc设置为-1,保证一个线程进行初始化
4.如果是初始化且hash对应索引位置为null,CAS操作new一个Node
5.如果当前节点正在移动即节点hash为-1,帮助移动
6.否则synchronized对当前桶加锁,执行与hashmap的put一样的操作,在链表尾部new一个node,或者插入树节点;然后判断是否需要转换为树,需要就树化并放入TreeBin容器,TreeBin继承Node且hash为-2;
怎么帮助移动,或者说多个线程怎么同时移动table:
1.每个线程判断当前节点hash是不是-1(已移动),或者整个移动是否完成;如果移动完成,直接返回移动后的table;
- 如果没有完成,用CAS将sizeCtl加1,增加一个参与扩容的线程数,然后当前线程参与扩容;
3.用当前计算机的cpu数目计算出一个步长,线程每次锁定等于步长大小的table长度(CAS),即锁定table的一段;
4.线程每次处理完自己锁定的部分table就去接着找,从原table的最高位索引向低位索引方向查找;
5.线程每次处理一个桶内的节点,并对同加synchronized锁,处理完就用将节点设置为已移动;
包括size 、 isEmpty和containsValue在内的聚合状态方法的结果通常仅在map未在其他线程中进行并发更新时才有用。
字段 sizeCtl 中的生成戳可确保调整大小不会重叠。因为我们使用的是二次幂扩展,所以每个 bin 中的元素必须保持相同的索引,或者以二次幂的偏移量移动。
网友评论