1. HashMap 不是线程安全的,那如果多线程下,它是如何处理的?并且什么 情况下会发生线程不安全的情况?

HashMap 不是线程安全的,如果多个线程同时对同一个 HashMap 更改数据的话,会导致数据不一致或者数据污染。

如果出现线程不安全的操作时,HashMap会尽可能的抛出 ConcurrentModificationException 防止数据异常。

当我们在对一个 HashMap 进行遍历时,在遍历期间,我们是不能对 HashMap 进行添加,删除等更改数据的操作的,否则也会抛出 ConcurrentModificationException 异常,此为 fail-fast(快速失败)机制。

从源码上分析,我们在 put,remove 等更改 HashMap数据时,都会导致 modCount 的改变,当 expectedModCount != modCount 时,则抛出 ConcurrentModificationException。如果想要线程安全,可以考虑使用ConcurrentHashMap

而且,在多线程下操作 HashMap,由于存在扩容机制,当 HashMap 调用resize()进行自动扩容时,可能会导致死循环的发生。

2. 我们在使用 HashMap 时,选取什么对象作为 key 键比较好

我们在使用 HashMap 时,最好选择不可变对象作为 key。例如 StringInteger 等不可变类型作为 key 是非常明智的。

如果 key 对象是可变的,那么 key 的哈希值就可能改变。在 HashMap 中可变对象作为 Key 会造成数据丢失。因为我们再进行 hash & (length - 1)取模运算计算位置查找对应元素时,位置可能已经发生改变,导致数据丢失。

3. 其他

HashMap 是基于 Map 接口实现的一种键-值对<key,value>的存储结构,允许 null 值,同时非有序,非同步(即线程不安全)。HashMap 的底层实现是数组 + 链表 + 红黑树(JDK1.8 增加了红黑树部分)。

HashMap 定位元素位置是通过键 key 经过扰动函数扰动后得到 hash 值,然后再通过 hash & (length - 1)代替取模的方式进行元素定位的。

HashMap 是使用链地址法解决 hash 冲突的,当有冲突元素放进来时,会将此元素插入至此位置链表的最后一位,形成单链表。当存在位置的链表长度 大于等于 8 时,HashMap 会将链表 转变为 红黑树,以此提高查找效率。

HashMap 的容量是 2n 次方,有利于提高计算元素存放位置时的效率,也降低了 hash 冲突的几率(当数组长度为 2n 次幂的时候,不同的 key 算出的 index 相同的几率较小,那么数据在数组上分布就比较均匀,也就是说碰撞的几率小,相对的,查询的时候就不用遍历某个位置上的链表,这样查询效率也就较高了。)。

因此,我们使用 HashMap 存储大量数据的时候,最好先预先指定容器的大小为 2n 次方,即使我们不指定为 2n次方,HashMap 也会把容器的大小设置成最接近设置数的 2n 次方,如,设置 HashMap 的大小为 7 ,则 HashMap 会将容器大小设置成最接近 7 的一个 2n 次方数,此值为 8

多线程环境下 ConcurrentHashMap 数据丢失 java_链表

多线程环境下 ConcurrentHashMap 数据丢失 java_HashMap_02