为什么HashMap是线程不安全的?
1、讲讲一下HashMap的存储结构
transient Node<K,V>[] table; static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Node<K,V> next; }
可以看到HashMap内部存储使用了一个node数组(默认大小是16),而Node类包含一个Node类型的next变量,指向下一个node相当于一个链表,所有根据Hash值计算的key一样的的数据会存储到一个bucket(桶)中(一个链表中),如下图
注意:java8中如果hash值相同的key的数量大于容量(默认8)时,会使用平衡树代替链表。
2、讲讲HashMap的扩容机制
HashMap内部的node数组默认是16,假设有100万个元素,每个桶(node)中平均存储62500个元素,那么Map的remove,get,put等方法的效率也会下降,为了解决这个问题,hashMap提供了自动扩容机制,当bucket个数超过容量(默认16)*loadfactor(默认0.75)之后,会把数组大小扩展为2*16=32个,并重新计算每个元素在在新数组中的位置。
图来源http://coding-geek.com/how-does-a-hashmap-work-in-java/
从图中可以看出,在已知key的情况下,没扩容之前获取EntryE需要遍历5次,扩容之后获取EntryE需要2次。
3、讲讲为什么线程不安全
两个方法,两个并发线程同时put元素,如果根据key值计算的hash值一样,那么put的数据会被覆盖;如果两个线程同时检测到当前数组大小大于容量*loadactory之后,会同时扩容并重新计算元素的新位置,并更新数组,这样最后就会出现有一个线程的数据会丢失。HashMap在线程并发时会容易死循环,死循环不是发生在put时而发生在扩容时。
4、讲讲如何安全的使用HashMap
//Hashtable Map<String, String> hashtable = new Hashtable<>(); //synchronizedMap Map<String, String&g