重要概念
- Capacity HashMap当前长度,为2的N次幂
- LoadFactor 负载因子 一般为0.75
- threshold
Capacity * LoadFactor
JDK8的hashmap改进
采用了位桶+链表/红黑树的方式,当某个位桶长度超过8时,链表转红黑树
采用了head和tail来保证链表顺序和之前一致,之前采用的rehash会倒置链表元素。但是还是会有数据丢失(并发情况),所以并发情况还是推荐使用ConcurrentHashMap
何时需要扩容
HashMap.Size >= Capacity * LoadFactor
,扩容为原先的2倍大小
hash值计算
扩容步骤Rehash(JDK8 之前)
通过单词意思可以知道这是个重新hash计算的过程
JDK8之后存在的并发问题
- put的时候导致多线程数据不一样
假如A线程计算出要落到hash桶的索引i,然后进入了睡眠,这个时候B线程,计算出了和A线程同样的hash桶索引i,然后B线程插入了数据在索引i,此时A线程苏醒,它还以为索引i中并没有数据,所以插入了数据在索引i,这样就覆盖了B线程在索引i插入的数据
- 触发fail-fast
线程A在迭代,线程B在插入删除操作,会抛出异常java.util.ConcurrentModificationException
类似于直接使用foreach迭代数组,边迭代边删除,一样会报错java.util.ConcurrentModificationException(并发修改异常)
因为使用foreach时,会创建两个线程,一个用来迭代数组为线程A,一个用来新增删除为线程B,B每次检查A线程中元素个数是否和自己相同,不同则抛出异常
此时需要使用Iterator
迭代器,即可解决边迭代边增删问题
1 | Iterator it = list.iterator(); |
- 并发扩容数据丢失
如果有多个线程同时发现需要扩容,但是最终只有一个线程扩容后的数组会赋给table