大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。
Jetbrains全系列IDE稳定放心使用
- 扩容
一般我们声明HashMap时,使用的都是默认的构造方法:HashMap<K,V>,看了代码你会发现,它还有其它的构造方法:
HashMap(int initialCapacity, float loadFactor),
其中参数initialCapacity为初始容量,loadFactor为加载因子,扩容就是在put加入元素的个数超过initialCapacity * loadFactor的时候就会将内部Entry数组大小扩大至原来的2倍,然后将数组元素按照新的数组大小重新计算索引,放在新的数组中,同时修改每个节点的链表关系(主要是next和节点在链表中的位置)。
假设这里有两个线程同时执行了put()操作,并进入了transfer()环节:
刚开始:
线程1中的e指向key(0),next指向key(4),此时线程1挂起。
线程2调度完成所有节点的移动,移动后结果为:
线程1继续执行,线程一会把线程二的新表当成原始的hash表,将原来e指向的key(0)节点当成是线程二中的key(0),放在自己所建table[0]的头节点。注意线程1的next仍然指向key(4),
虽然此时key(0)的next已经是null。
- 执行e.next = newTable[i],于是 key(0)的 next 指向了线程1的新 Hash 表,因为新 Hash 表为空,所以e.next = null,
- 执行newTable[i] = e,所以线程1的新 Hash 表第一个元素指向了线程2新 Hash 表的 key(0)。好了,e 处理完毕。
- 执行e = next,将 e 指向 next,所以新的 e 是 key(4)
线程1的e指向了上一次循环的next,也就是key(4),此时key(4)的next已经是key(0)。将key(4)插入到table[0]的头节点,并且将key(4)的next设置为key(0)。此时仍然没有问题。
- 现在的 e 节点是 key(4),首先执行Entry<K,V> next = e.next,那么 next 就是 key(0)了
- 执行e.next = newTable[i],于是key(0) 的 next 就成了 key(4)
- 执行newTable[i] = e,那么线程1的新 Hash 表第一个元素变成了 key(4)
- 执行e = next,将 e 指向 next,所以新的 e 是 key(0)
- 现在的 e 节点是 key(0),首先执行Entry<K,V> next = e.next,那么 next 就是 null
- 执行e.next = newTable[i],于是key(0) 的 next 就成了 key(4)
- 执行newTable[i] = e,那么线程1的新 Hash 表第一个元素变成了 key(0)
- 执行e = next,将 e 指向 next,所以新的 e 是 key(4)
总结版:HashMap在put的时候,插入的元素超过了容量(由负载因子决定)的范围就会触发扩容操作,就是rehash,这个会重新将原数组的内容重新hash到新的扩容数组中,在多线程的环境下,存在同时其他的元素也在进行put操作,如果hash值相同,可能出现同时在同一数组下用链表表示,造成闭环,导致在get时会出现死循环,所以HashMap是线程不安全的。
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/183168.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...