参考地址:https://blog.csdn.net/zly9923218/article/details/51497497
hashmap啥时候是不安全的
扩容
一个线程对HashMap进行扩容,另外一个线程读取HashMap的值,扩容可能导致数组table的length变了。比如,key原来对应2的位置,扩容后变成了18位置,相当于数组长度从16变成了32。
而一个线程在扩容之前取得了数组length是16,而在table[index]的时候刚好扩容完成,还是从2的位置读取元素,那肯定读出来的是null,而实际上HashMap中是有值的。
put的时候导致的多线程数据不一致
比如有两个线程A和B,首先A希望插入一个key-value对到HashMap中,首先计算记录所要落到的桶的索引坐标,然后获取到该桶里面的链表头结点,此时线程A的时间片用完了,而此时线程B被调度得以执行,和线程A一样执行,只不过线程B成功将记录插到了桶里面,假设线程A插入的记录计算出来的桶索引和线程B要插入的记录计算出来的桶索引是一样的,那么当线程B成功插入之后,线程A再次被调度运行时,它依然持有过期的链表头但是它对此一无所知,以至于它认为它应该这样做,如此一来就覆盖了线程B插入的记录,这样线程B插入的记录就凭空消失了,造成了数据不一致的行为。
ConcurrentHashMap
扩容
为什么ConcurrentHashMap可以做到在扩容的时候读取正确的值呢?
ConcurrentHashMap中在扩容的时候,会复制原来数组的元素table到nextTable,复制过程会对原有的链表头结点加sychronize。
在每个节点链表复制完成之后,把原有节点变成forwardingNode,指向nextTable。
这样,在复制过程中,get函数依然能获取正确的值:
1、该数组节点没有被复制完,那么则会查找Node类型的链表头结点
2、该数组节点已经被复制完,那么则会查找nextTable中的元素
如果该数组节点没有被复制完,且没有sychronized,那么说明还没有复制到这个节点,(因为节点复制是从数组下标为0开始的,逐个递增)那么可以在该节点的链表进行插入,插入需要加sychronized。
如果该数组节点没有复制完,且有sychronized,那么插入操作只能等待,直到获得锁。等该节点变成forwardingNode,就能获得锁,进行插入。这时插入操作会插入到nextTable中了。
如果该数组节点已经被复制完,那么是forwardingNode类型,需要插入到nextTable中了。
插入数据
concurrentHashMap将每个槽都加上了锁,所以插入数据时,Hash函数到相同的槽时,都必须等待,直到上一个操作完成。
