Java容器來一發(五)ConcurrentHashMap
1、ConcurrentHashMap簡介
ConcurrentHashMap採用了分段鎖的設計,只有在同一個分段內才存在競態關係,不同的分段鎖之間沒有鎖競爭,因此相對於HashMap效率更高。也就是說ConcurrentHashMap可以做到讀取資料不加鎖,並且其內部的結構可以讓其在進行寫操作的時候能夠將鎖的粒度保持地儘量地小,不用對整個ConcurrentHashMap加鎖。
ConcurrentHashMap內部分成了很多段,即segments,每個段相當於一個HashTable。讀操作不加鎖,寫操作只鎖一個segment,所以效率較高。
2、get方法
get操作不需要鎖,除非讀到的值是空的才會加鎖重讀,原因是它的get方法裡將要使用的共享變數都定義成volatile,而get操作裡只需要讀不需要寫共享變數count和value。
get方法分三步:
第一步,訪問count變數,它是volatile變數,對於增刪操作,由於其最終都會寫count 變數,因此訪問count變數可以拿到準確值;對於非增刪操作,也就是結點值的改變,由於HashEntry的value變數是 volatile的,也能保證讀取到最新的值。
第二步,getFirst找到頭指標,對hash鏈進行遍歷找到要獲取的結點,如果沒有找到,直接訪回null。對hash鏈進行遍歷不需要加鎖的原因在於鏈指標next是final的。但getFirst(hash)可能返回過時的頭結點,這是可以接受的。如果要得到最新的資料,只有採用完全的同步。
第三步,如果找到了所需結點,判斷它的值如果非空就直接返回,否則在有鎖的狀態下再讀一次。
3、put方法
put是在持有段鎖的情況下執行的,由於put方法裡需要對共享變數(count)進行寫入操作,所以為了執行緒安全,在操作共享變數時必須得加鎖。put方法首先定位到Segment,然後在Segment裡進行插入操作,插入操作分兩步,第一步判斷是否需要對Segment裡的HashEntry陣列進行擴容,第二步定位新增元素的位置然後放在HashEntry數組裡。
4、size方法
執行size方法時,如果為了保證執行緒安全,將put,remove等方法全部鎖住,顯然會非常低效。ConcurrentHashMap的思路是:因為在累加各Segment的count過程中之前累加過的count發生變化的機率比較小,所以先嚐試兩次通過不鎖住Segment的方式來統計各個Segment大小,如果統計的過程中,容器的count發生了變化,則再採用加鎖的方式來統計所有Segment的大小。如何判斷統計的時候count是否發生了變化?ConcurrentHashMap中有一個全域性的modCount變數,所有的增刪變更都會修改這個變數,通過它可以判斷。
參考資料: