背景
- HashTable繼承Dictionary類,是線程安全的,但是效率低,因為HashTable使用了synchronized,所有線程經(jīng)常同一把鎖
- ConcurrentHashMap是線程安全而且效率高,因為它包含了一個Segment數(shù)組,將數(shù)據(jù)分段存儲,給每一段詩句配一把鎖,也就是所謂的所分段技術(shù)
- JDK7采用了位桶+鏈表的方式,即散列鏈表的方式實現(xiàn)的;JDK8采用的是位桶+鏈表/紅黑樹的方式實現(xiàn),也是非線程安全。當(dāng)某個位桶的鏈表的長度達(dá)到某個閥值時,這個鏈表就轉(zhuǎn)換為紅黑樹
HashMap為什么線程不安全
HashMap的自動擴(kuò)容機(jī)制
HashMap內(nèi)部存儲使用了一個Node數(shù)組,而Node類包含一個類型為Node的next變量,也就是相當(dāng)于一個鏈表,所有的hash值相同(即產(chǎn)生了沖突)的key值會存儲到同一個鏈表中。HashMao內(nèi)部的Node數(shù)組默認(rèn)的大小是16,默認(rèn)的負(fù)載因子是0.75,當(dāng)HashMap中的元素超過16\0.75=12時,會把數(shù)組擴(kuò)展為2*16=32,并重新計算每個元素在新數(shù)組中的位置
并發(fā)下的HashMap使用
- 如果多個線程同時使用put方法添加元素,而且假設(shè)正好存在兩個put的key值發(fā)生了碰撞,即hash值一樣,那么根據(jù)hashmap的實現(xiàn),這兩個key會添加到數(shù)組的同一個位置,這樣會造成其中一個線程的put的數(shù)據(jù)會被覆蓋。
- 如果多個線程同時檢測到元素個數(shù)超過數(shù)組大小*loadfactor,這樣會發(fā)生多個線程同時對Node數(shù)組進(jìn)行擴(kuò)容,都在重新計算元素位置以及復(fù)制數(shù)據(jù),但是最終只有一個線程擴(kuò)容后的數(shù)組會賦值給table,換言之,其他線程的都會丟失,并且各自線程的put數(shù)據(jù)也丟失了
HashMap在并發(fā)執(zhí)行put操作時會引起死循環(huán),導(dǎo)致CPU利用率接近100%,因>為多線程會導(dǎo)致hashmap中的Node鏈表形成環(huán)形數(shù)據(jù)結(jié)構(gòu),一旦形成環(huán)形數(shù)據(jù)結(jié)構(gòu),Node的next節(jié)點永遠(yuǎn)不為空,就會在獲取node時候產(chǎn)生死循環(huán)
如何線程安全使用
- 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>());