为什么HashMap是线程不安全的
1.如果多个线程同时使用put方法添加元素会丢失元素
假设正好存在两个put的key发生了碰撞,那么根据HashMap的实现,这两个key会添加到数组的同一个位置,这样最终就会发生其中一个线程的put的数据被覆盖。
2.多线程同时扩容会造成死循环
多线程同时检查到扩容,并且执行扩容操作,在进行rehash的时候会造成闭环链表,从而在get该位置元素的时候,程序将会进入死循环。【证明HashMap高并发下问题会在以后的文章中出现】
如何让HashMap实现线程安全?
- 直接使用Hashtable
- Collections.synchronizeMap方法
- 使用ConcurrentHashMap
总结
- HashMap 在第一次 put 时初始化,类似 ArrayList 在第一次 add 时分配空间。
- HashMap 的 bucket 数组大小一定是2的n次方
- HashMap 在 put 的元素数量大于 Capacity * LoadFactor(默认16 * 0.75) 之后会进行扩容
- 负载因子是可以修改的,也可以大于1,但是建议不要轻易修改,除非情况非常特殊
- JDK8处于提升性能的考虑,在哈希碰撞的链表长度达到TREEIFY_THRESHOLD(默认8)后,会把该链表转变成树结构
- JDK8在 resize 的时候,通过巧妙的设计,减少了 rehash 的性能消耗
- 扩容是一个特别耗性能的操作,所以当在使用HashMap的时候,估算map的大小,初始化的时候给一个大致的数值,避免map进行频繁的扩容
java1.8在hashmap的node长度大于8的时候,链表就会装化成红黑树的数据结构来存储节点
java7ConcurrenHashMap相比HashTable,多了一层segment数组,由于有了这一层,同步锁的粒度更细,每次的操作只对其中某个segment加锁,其他segment的节点是可操作的.而在java8中直接对链表和红黑树加锁,去掉了segment这一层。