J.U.C--分段鎖
前言:在分析ConcurrentHashMap的原始碼的時候,瞭解到這個併發容器類的加鎖機制是基於粒度更小的分段鎖,分段鎖也是提升多併發程式效能的重要手段之一。
在併發程式中,序列操作是會降低可伸縮性,並且上下文切換也會減低效能。在鎖上發生競爭時將通水導致這兩種問題,使用獨佔鎖時保護受限資源的時候,基本上是採用序列方式—-每次只能有一個執行緒能訪問它。所以對於可伸縮性來說最大的威脅就是獨佔鎖。
我們一般有三種方式降低鎖的競爭程度:
1、減少鎖的持有時間
2、降低鎖的請求頻率
3、使用帶有協調機制的獨佔鎖,這些機制允許更高的併發性。
在某些情況下我們可以將鎖分解技術進一步擴充套件為一組獨立物件上的鎖進行分解,這成為分段鎖。其實說的簡單一點就是:容器裡有多把鎖,每一把鎖用於鎖容器其中一部分資料,那麼當多執行緒訪問容器裡不同資料段的資料時,執行緒間就不會存在鎖競爭,從而可以有效的提高併發訪問效率,這就是ConcurrentHashMap所使用的鎖分段技術,首先將資料分成一段一段的儲存,然後給每一段資料配一把鎖,當一個執行緒佔用鎖訪問其中一個段資料的時候,其他段的資料也能被其他執行緒訪問。
比如:在ConcurrentHashMap中使用了一個包含16個鎖的陣列,每個鎖保護所有雜湊桶的1/16,其中第N個雜湊桶由第(N mod 16)個鎖來保護。假設使用合理的雜湊演算法使關鍵字能夠均勻的分部,那麼這大約能使對鎖的請求減少到越來的1/16。也正是這項技術使得ConcurrentHashMap支援多達16個併發的寫入執行緒。
當然,任何技術必有其劣勢,與獨佔鎖相比,維護多個鎖來實現獨佔訪問將更加困難而且開銷更加大。
下面給出一個基於雜湊的Map的實現,使用分段鎖技術。
package collection;
import java.util.Map;
/**
* Created by louyuting on 17/1/10.
*/
public class StripedMap {
//同步策略: buckets[n]由 locks[n%N_LOCKS] 來保護
private static final int N_LOCKS = 16;//分段鎖的個數
private final Node[] buckets;
private final Object[] locks;
/**
* 結點
* @param <K>
* @param <V>
*/
private static class Node<K,V> implements Map.Entry<K,V>{
final K key;//key
V value;//value
Node<K,V> next;//指向下一個結點的指標
int hash;//hash值
//構造器,傳入Entry的四個屬性
Node(int h, K k, V v, Node<K,V> n) {
value = v;
next = n;//該Entry的後繼
key = k;
hash = h;
}
public final K getKey() {
return key;
}
public final V getValue() {
return value;
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
}
/**
* 構造器: 初始化雜湊桶和分段鎖陣列
* @param numBuckets
*/
public StripedMap(int numBuckets) {
buckets = new Node[numBuckets];
locks = new Object[N_LOCKS];
for(int i=0; i<N_LOCKS; i++){
locks[i] = new Object();
}
}
/**
* 返回雜湊之後在雜湊桶之中的定位
* @param key
* @return
*/
private final int hash(Object key){
return Math.abs(key.hashCode() % N_LOCKS);
}
/**
* 分段鎖實現的get
* @param key
* @return
*/
public Object get(Object key){
int hash = hash(key);//計算hash值
//獲取分段鎖中的某一把鎖
synchronized (locks[hash% N_LOCKS]){
for(Node m=buckets[hash]; m!=null; m=m.next){
if(m.key.equals(key)){
return m.value;
}
}
}
return null;
}
/**
* 清除整個map
*/
public void clear() {
//分段獲取雜湊桶中每個桶地鎖,然後清除對應的桶的鎖
for(int i=0; i<buckets.length; i++){
synchronized (locks[i%N_LOCKS]){
buckets[i] = null;
}
}
}
}
上面的實現中:使用了N_LOCKS個鎖物件陣列,並且每個鎖保護容器的一個子集,對於大多數的方法只需要回去key值的hash雜湊之後對應的資料區域的一把鎖就行了。但是對於某些方法卻要獲得全部的鎖,比如clear()方法,但是獲得全部的鎖不必是同時獲得,可以使分段獲得,具體的檢視原始碼。
這就是分段鎖的思想。