背景
線程不安全的HashMap
因為多線程環(huán)境下撼唾,使用Hashmap進行put操作會引起死循環(huán)壕翩,導(dǎo)致CPU利用率接近100%,所以在并發(fā)情況下不能使用HashMap稠诲。
效率低下的HashTable容器
HashTable容器使用synchronized來保證線程安全捎泻,但在線程競爭激烈的情況下HashTable的效率非常低下飒炎。
因為當(dāng)一個線程訪問HashTable的同步方法時,其他線程訪問HashTable的同步方法時笆豁,可能會進入阻塞或輪詢狀態(tài)郎汪。如線程1使用put進行添加元素定欧,線程2不但不能使用put方法添加元素,并且也不能使用get方法來獲取元素怒竿,所以競爭越激烈效率越低。
鎖分段技術(shù)
ConcurrentHashMap所使用的鎖分段技術(shù)扩氢,首先將數(shù)據(jù)分成一段一段的存儲耕驰,然后給每一段數(shù)據(jù)配一把鎖,當(dāng)一個線程占用鎖訪問其中一個段數(shù)據(jù)的時候录豺,其他段的數(shù)據(jù)也能被其他線程訪問朦肘。
有些方法需要跨段,比如size()和containsValue()双饥,它們可能需要鎖定整個表而而不僅僅是某個段媒抠,這需要按順序鎖定所有段,操作完畢后咏花,又按順序釋放所有段的鎖趴生。
這里“按順序”是很重要的,否則極有可能出現(xiàn)死鎖昏翰,在ConcurrentHashMap內(nèi)部苍匆,段數(shù)組是final的,并且其成員變量實際上也是final的棚菊,但是浸踩,僅僅是將數(shù)組聲明為final的并不保證數(shù)組成員也是final的,這需要實現(xiàn)上的保證统求。這可以確保不會出現(xiàn)死鎖检碗,因為獲得鎖的順序是固定的。
ConcurrentHashMap是由Segment數(shù)組結(jié)構(gòu)和HashEntry數(shù)組結(jié)構(gòu)組成码邻。Segment是一種可重入鎖ReentrantLock折剃,在ConcurrentHashMap里扮演鎖的角色,HashEntry則用于存儲鍵值對數(shù)據(jù)像屋。
一個ConcurrentHashMap里包含一個Segment數(shù)組微驶,Segment的結(jié)構(gòu)和HashMap類似,是一種數(shù)組和鏈表結(jié)構(gòu)开睡, 一個Segment里包含一個HashEntry數(shù)組因苹,每個HashEntry是一個鏈表結(jié)構(gòu)的元素, 每個Segment守護者一個HashEntry數(shù)組里的元素,當(dāng)對HashEntry數(shù)組的數(shù)據(jù)進行修改時篇恒,必須首先獲得它對應(yīng)的Segment鎖扶檐。
應(yīng)用場景
當(dāng)有一個大數(shù)組時需要在多個線程共享時就可以考慮是否把它給分層多個節(jié)點了,避免大鎖胁艰。并可以考慮通過hash算法進行一些模塊定位款筑。
解讀
不變和易變
ConcurrentHashMap完全允許多個讀操作并發(fā)進行智蝠,讀操作并不需要加鎖。如果使用傳統(tǒng)的技術(shù)奈梳,如HashMap中的實現(xiàn)杈湾,如果允許可以在hash鏈的中間添加或刪除元素,讀操作不加鎖將得到不一致的數(shù)據(jù)攘须。
ConcurrentHashMap實現(xiàn)技術(shù)是保證HashEntry幾乎是不可變的漆撞。
可以看到除了value不是final的,其它值都是final的于宙,這意味著不能從hash鏈的中間或尾部添加或刪除節(jié)點浮驳,因為這需要修改next 引用值,所有的節(jié)點的修改只能從頭部開始捞魁。對于put操作至会,可以一律添加到Hash鏈的頭部。
但是對于remove操作谱俭,可能需要從中間刪除一個節(jié)點奉件,這就需要將要刪除節(jié)點的前面所有節(jié)點整個復(fù)制一遍,最后一個節(jié)點指向要刪除結(jié)點的下一個結(jié)點昆著。這在講解刪除操作時還會詳述瓶蚂。為了確保讀操作能夠看到最新的值,將value設(shè)置成volatile宣吱,這避免了加鎖窃这。
數(shù)據(jù)結(jié)構(gòu)
Hash表的一個很重要方面就是如何解決hash沖突,ConcurrentHashMap 和HashMap使用相同的方式征候,都是將hash值相同的節(jié)點放在一個hash鏈中杭攻。與HashMap不同的是,ConcurrentHashMap使用多個子Hash表疤坝,也就是段(Segment)兆解。