同步容器和并發(fā)容器有用過嗎?說說看
同步容器
在之前講Java基礎的時候給大家講過集合容器框架,比如Arraylist,LinkedLsit這些熟知的,它們都不是線程安全的。在多線程環(huán)境中,去訪問這些容器就會出現并發(fā)安全問題。
那什么是同步容器,可以先簡單的理解通過使用鎖來實現同步的容器,主要的同步容器類有:
- Vector
- Stack
- HashTable
- Collections.synchronizedXXX(組成的方法)
這里給大家介紹一下Vector,很簡單,它也是實現了List接口,我們看下它的add() 和 get()方法
public synchronized E get(int index) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
return elementData(index);
}
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
Vector和Hashtable是線程安全的容器類,實現同步的方式是通過對方法加鎖(sychronized)方式實現的,這樣讀寫均需要鎖操作,導致性能低下。
而即使是Vector這樣線程安全的類,在面對多線程下的復合操作的時候也是需要通過客戶端加鎖的方式保證原子性
并發(fā)容器
上面我們聊到到同步容器有一些性能的缺點,針對不同的場景,為了提高容器的并發(fā)訪問,所以我們往往會使用并發(fā)容器。
例如上節(jié)給大家講的BlockingQueue其實它也是并發(fā)容器的一種,例如CopyOnWrite容器,這里不給大家過多介紹,可以自行查閱。我們重點要說的是并發(fā)Map
ConcurrentMap接口
public interface ConcurrentMap<K, V> extends Map<K, V> {
//插入元素
V putIfAbsent(K key, V value);
//移除元素
boolean remove(Object key, Object value);
//替換元素
boolean replace(K key, V oldValue, V newValue);
//替換元素
V replace(K key, V value);
}
- 「putIfAbsent:」如果插入的key相同,則不替換原有的value值;
- 「remove:」增加了對value的判斷,如果要刪除的key-value不能與Map中原有的key-value對應上,則不會刪除該元素;
- 「replace(K,V,V):」增加了對value值的判斷,如果key-oldValue能與Map中原有的key-value對應上,才進行替換操作;
- 「replace(K,V):」與上面的replace不同的是,此replace不會對Map中原有的key-value進行比較,如果key存在則直接替換;
ConcurrentHashMap
ConcurrentHashMap同HashMap一樣也是基于散列表的map
JDK 1.7
ConcurrentHashMap在JDK 1.7中,提供了一種粒度更細的加鎖機制來實現在多線程下更高的性能,這種機制叫分段鎖(Lock Striping)。
提供的優(yōu)點是:在并發(fā)環(huán)境下將實現更高的吞吐量,而在單線程環(huán)境下只損失非常小的性能。
分段鎖就是「將數據分段,對每一段數據分配一把鎖」。當一個線程占用鎖訪問其中一個段數據的時候,其他段的數據也能被其他線程訪問。
有些方法需要跨段,比如size()、isEmpty()、containsValue(),它們可能需要鎖定整個表而不僅僅是某個段,這需要按順序鎖定所有段,操作完畢后,又按順序釋放所有段的鎖
ConcurrentHashMap是由Segment數組結構和HashEntry數組結構組成。Segment是一種可重入鎖ReentrantLock,HashEntry則用于存儲鍵值對數據。
一個ConcurrentHashMap里包含一個Segment數組,Segment的結構和HashMap類似,是一種數組和鏈表結構, 一個Segment里包含一個HashEntry數組,每個HashEntry是一個鏈表結構的元素, 每個Segment守護著一個HashEntry數組里的元素,當對HashEntry數組的數據進行修改時,必須首先獲得它對應的Segment鎖。
JDK 1.8
而在JDK 1.8中,ConcurrentHashMap主要做了兩個優(yōu)化:
同HashMap一樣,鏈表也會在長度達到8的時候轉化為紅黑樹,這樣可以提升大量沖突時候的查詢效率;
以某個位置的頭結點(鏈表的頭結點或紅黑樹的root結點)為鎖,配合自旋+CAS避免不必要的鎖開銷,進一步提升并發(fā)性能。
ConcurrentNavigableMap接口與ConcurrentSkipListMap類
ConcurrentNavigableMap接口繼承了NavigableMap接口,這個接口提供了針對給定搜索目標返回最接近匹配項的導航方法。
ConcurrentNavigableMap接口的主要實現類是ConcurrentSkipListMap類。從名字上來看,它的底層使用的是跳表(SkipList)的數據結構。它是一種”空間換時間“的數據結構,可以使用CAS來保證并發(fā)安全性。
并發(fā)Set
JDK提供了ConcurrentSkipListSet,是線程安全的有序的集合。底層是使用ConcurrentSkipListMap實現。
結束語
本節(jié)主要給大家講了同步容器和并發(fā)容器,在并發(fā)容器中,大家要重點關注ConcurrentHashMap,在本節(jié)中直接給大家講了它的機制,聽起來可能有點懵圈。