Java集合大致可分為Set、List和Map三種體系,其中Set代表無序、不可重復的集合;List代表有序、重復的集合;而Map則代表具有映射關系的集合。Java 5之后,增加了Queue體系集合,代表一種隊列集合實現。
JDK1.5版本中,加入java.uill.concurrent包,其中包含集合的線程安全方式的實現。本文僅探討concurrent包下面的Map接口實現。
1. concurrent包下面Map子接口、類框架圖
2. ConcurrentMap接口、ConcurrentHashMap類
2.1 它是什么?
ConcurrentHashMap是一個線程安全的哈希表,它的主要功能是提供了一組和HashMap功能相同但是線程安全的方法。ConcurrentHashMap可以做到讀取數據不加鎖,並且其內部的結構可以讓其在進行寫操作的時候能夠將鎖的粒度保持地盡量地小,不用對整個ConcurrentHashMap加鎖。
2.2 它的工作原理?
ConcurrentHashMap為了提高本身的並發能力,在內部采用了一個叫做Segment的結構,一個Segment其實就是一個類Hash Table的結構,Segment內部維護了一個鏈表數組,我們用下面這一幅圖來看下ConcurrentHashMap的內部結構:
從上面的結構我們可以了解到,ConcurrentHashMap定位一個元素的過程需要進行兩次Hash操作,第一次Hash定位到Segment,第二次Hash定位到元素所在的鏈表的頭部,因 此,這一種結構的帶來的副作用是Hash的過程要比普通的HashMap要長,但是帶來的好處是寫操作的時候可以只對元素所在的Segment進行加鎖即可,不會影響到其他的 Segment,這樣,在最理想的情況下,ConcurrentHashMap可以最高同時支持Segment數量大小的寫操作(剛好這些寫操作都非常平均地分布在所有的Segment上),所以,通過這一種結構,ConcurrentHashMap的並發能力可以大大的提高。
2.3 它實現的ConcurrentMap接口方法:
- V putIfAbsent(K key, V value): 如果指定鍵已經不再與某個值相關聯,則將它與給定值關聯;推薦使用該方法,而不是使用Map接口的put()方法。
- boolean remove(Object key, Object value): 只有目前將鍵的條目映射到給定值時,才移除該鍵的條目。
- boolean replace(K key, V oldValue, V newValue): 只有目前將鍵的條目映射到給定值時,才替換該鍵的條目。
- V replace(K key, V value): 只有目前將鍵的條目映射到某一值時,才替換該鍵的條目。
2.4 它實現的Map接口主要方法:
- V get(Object key): 返回此表中指定鍵所映射到的值;此操作不涉及到鎖,也就是說獲得對象時沒有使用鎖。
- Set<Map.Entry<K,V>> entrySet(): 該視圖的返回的 iterator 是一個“弱一致(weakly consistent)”的迭代器,它從來不會拋出 ConcurrentModificationException,並且確保可遍歷迭代器構造時存在的元素,此外還可能(但並不保證)反映構造后的所有修改。
2.5 ConcurrentMap接口的putIfAbsent()與remove()方法詳解:
putIfAbsent() 方法用於在 map 中進行添加。這個方法以要添加到 ConcurrentMap 中的鍵的值為參數,就像普通的 put() 方法,但是只有在 map 不包含這個鍵時,才能將鍵加入到 map 中。如果 map 已經包含這個鍵,那么這個鍵的現有值就會保留。 putIfAbsent() 方法是原子的。等價於下面的代碼(除了原子地執行此操作之外):
if (!map.containsKey(key)) return map.put(key, value); else return map.get(key);
remove() 方法有兩個參數:鍵和值。在調用時,只有當鍵映射到指定的值時才從 map 中刪除這個鍵。如果不匹配,那么就不刪除這個鍵,並返回 false。如果值匹配鍵的當前映射內容,那么就刪除這個鍵,這個方法是原子性的。這種操作的等價源代碼(除了原子地執行此操作之外):
if (map.get(key).equals(value)) { map.remove(key); return true; } else return false;
由此可以看出,java.util.concurrent.ConcurrentMap 接口和 ConcurrentHashMap 實現類可以實現只能在鍵不存在時將元素加入到map中;只有鍵存在且映射到特定值時才能從map中刪除一個元素。