背景
- HashTable继承Dictionary类,是线程安全的,但是效率低,因为HashTable使用了synchronized,所有线程经常同一把锁
- ConcurrentHashMap是线程安全而且效率高,因为它包含了一个Segment数组,将数据分段存储,给每一段诗句配一把锁,也就是所谓的所分段技术
- JDK7采用了位桶+链表的方式,即散列链表的方式实现的;JDK8采用的是位桶+链表/红黑树的方式实现,也是非线程安全。当某个位桶的链表的长度达到某个阀值时,这个链表就转换为红黑树
HashMap为什么线程不安全
HashMap的自动扩容机制
HashMap内部存储使用了一个Node数组,而Node类包含一个类型为Node的next变量,也就是相当于一个链表,所有的hash值相同(即产生了冲突)的key值会存储到同一个链表中。HashMao内部的Node数组默认的大小是16,默认的负载因子是0.75,当HashMap中的元素超过16\0.75=12时,会把数组扩展为2*16=32,并重新计算每个元素在新数组中的位置
并发下的HashMap使用
- 如果多个线程同时使用put方法添加元素,而且假设正好存在两个put的key值发生了碰撞,即hash值一样,那么根据hashmap的实现,这两个key会添加到数组的同一个位置,这样会造成其中一个线程的put的数据会被覆盖。
- 如果多个线程同时检测到元素个数超过数组大小*loadfactor,这样会发生多个线程同时对Node数组进行扩容,都在重新计算元素位置以及复制数据,但是最终只有一个线程扩容后的数组会赋值给table,换言之,其他线程的都会丢失,并且各自线程的put数据也丢失了
HashMap在并发执行put操作时会引起死循环,导致CPU利用率接近100%,因>为多线程会导致hashmap中的Node链表形成环形数据结构,一旦形成环形数据结构,Node的next节点永远不为空,就会在获取node时候产生死循环
如何线程安全使用
- hashtable
Map<String, String> hashtable = new Hashtable<>();
- Concurrenthashmap
Map<String, String> concurrentHashMap = new ConcurrentHashMap<>();
- SyncronizedMap
Map<String, String> synchronizedHashMap = Collections.synchronizedMap(new HashMap<String, String>());