背景
- HashTable繼承Dictionary類芽卿,是線程安全的蹬竖,但是效率低币厕,因?yàn)镠ashTable使用了synchronized旦装,所有線程經(jīng)常同一把鎖
- ConcurrentHashMap是線程安全而且效率高,因?yàn)樗艘粋€(gè)Segment數(shù)組艰躺,將數(shù)據(jù)分段存儲(chǔ)眨八,給每一段詩(shī)句配一把鎖廉侧,也就是所謂的所分段技術(shù)
- JDK7采用了位桶+鏈表的方式,即散列鏈表的方式實(shí)現(xiàn)的闰蚕;JDK8采用的是位桶+鏈表/紅黑樹(shù)的方式實(shí)現(xiàn)没陡,也是非線程安全索赏。當(dāng)某個(gè)位桶的鏈表的長(zhǎng)度達(dá)到某個(gè)閥值時(shí)参滴,這個(gè)鏈表就轉(zhuǎn)換為紅黑樹(shù)
HashMap為什么線程不安全
HashMap的自動(dòng)擴(kuò)容機(jī)制
HashMap內(nèi)部存儲(chǔ)使用了一個(gè)Node數(shù)組,而Node類包含一個(gè)類型為Node的next變量青灼,也就是相當(dāng)于一個(gè)鏈表,所有的hash值相同(即產(chǎn)生了沖突)的key值會(huì)存儲(chǔ)到同一個(gè)鏈表中专普。HashMao內(nèi)部的Node數(shù)組默認(rèn)的大小是16檀夹,默認(rèn)的負(fù)載因子是0.75炸渡,當(dāng)HashMap中的元素超過(guò)16\0.75=12時(shí),會(huì)把數(shù)組擴(kuò)展為2*16=32蚌堵,并重新計(jì)算每個(gè)元素在新數(shù)組中的位置
并發(fā)下的HashMap使用
- 如果多個(gè)線程同時(shí)使用put方法添加元素督赤,而且假設(shè)正好存在兩個(gè)put的key值發(fā)生了碰撞躲舌,即hash值一樣,那么根據(jù)hashmap的實(shí)現(xiàn)毅贮,這兩個(gè)key會(huì)添加到數(shù)組的同一個(gè)位置藻丢,這樣會(huì)造成其中一個(gè)線程的put的數(shù)據(jù)會(huì)被覆蓋瑰煎。
- 如果多個(gè)線程同時(shí)檢測(cè)到元素個(gè)數(shù)超過(guò)數(shù)組大小*loadfactor,這樣會(huì)發(fā)生多個(gè)線程同時(shí)對(duì)Node數(shù)組進(jìn)行擴(kuò)容酒甸,都在重新計(jì)算元素位置以及復(fù)制數(shù)據(jù)沽瘦,但是最終只有一個(gè)線程擴(kuò)容后的數(shù)組會(huì)賦值給table析恋,換言之助隧,其他線程的都會(huì)丟失并村,并且各自線程的put數(shù)據(jù)也丟失了
HashMap在并發(fā)執(zhí)行put操作時(shí)會(huì)引起死循環(huán)哩牍,導(dǎo)致CPU利用率接近100%姐叁,因>為多線程會(huì)導(dǎo)致hashmap中的Node鏈表形成環(huán)形數(shù)據(jù)結(jié)構(gòu)外潜,一旦形成環(huán)形數(shù)據(jù)結(jié)構(gòu)处窥,Node的next節(jié)點(diǎn)永遠(yuǎn)不為空谒麦,就會(huì)在獲取node時(shí)候產(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>());