ConcurrentHashMap的扩容为了提高效率,是多线程并发的
每个线程控制一部分范围节点的扩容(根据cpu与数组长度确定控制多大范围)
有两个核心参数
sizeCtl:标记扩容状态 负数时代表正在扩容,存储量参与扩容的线程数,正数代表出发扩容的阈值
transferIndex:表示当前等待迁移的旧表的最大索引(由高到低迁移 )若map数组长度为16则就该值的初时值是15
需要注意的是进行扩容的线程并不是Map自己创建的,而是抓的壮丁!谁触发,谁就被抓来协助扩容。
既然是多线程并发,那就一定是每个线程负责一部分,怎么确定哪些线程负责那些部分,避免重复迁移呢?这就要用到transferIndex,
所有线程尝试去获取自己负责的部分时,都要尝试cas修改这个值,注意每次的获取是分块的,有一个stide 每个线程至少是16个,即16个桶,cas修改transferIndex后就开始迁移元素。
元素的迁移过程中,线程会依次获取自己负责范围内的锁桶,注意不是一口气全拿,而是迁移一个拿一个,迁移完就释放,确保其他线程可正常读写还没有被迁移的桶。迁移完后的桶的头结点会被标记成ForwardingNode,表示该桶已被迁移,此时若有线程读取该桶的数据,则会到新表中取查询。
迁移时也会根据高低位来判断是否要迁移,将旧桶的链表拆分为两个链表(低位链表和高位链表),分别放到新表的
i
和i + oldCapacity
位置。红黑树迁移:类似链表,但需检查拆分后的节点数量,若小于
UNTREEIFY_THRESHOLD
(默认 6),则退化为链表迁移完成前并不会真正断开旧表对元素的引用,这样对与正在迁移的桶,可以直接get其中的元素
最后完成扩容后会用nexttable 替换旧表
并修改sizeCtl会新容量的0.75倍
迁移过程中的put和get
get:若未被迁移直接读,被迁移(发现头结点是ForwardingNode,会跳转到对应的新表节点)从新表中读
put:会主动协助扩容,写操作一定要获取到桶锁