線程安全的HashMap
一、一般模式下線程安全的HashMap
默認情況常用的HashMap都是線程不安全的,在多線程的環境下使用,常常會造成不可預知的,莫名其妙的錯誤。那麽,我們如何實現一個線程安全的HashMap呢?其中一個可行的方式是使用Collectons.synchronizedMap() 方法來包裝我們的HashMap。如下:
Map<String, String> map = Collections.synchronizedMap(new HashMap<String,String>());
Collections.synchronizedMap()會生成一個SynchronizedMap,它使用委托模式,將自己HashMap相關的功能交給傳入HashMap實現,二自己負責線程安全的相關實現,下面看看
SynchronizedMap的定義:
private static class SynchronizedMap<K,V> implements Map<K,V>, Serializable { private static final long serialVersionUID = 1978198479659022715L; private final Map<K,V> m; // Backing Map // 使用 mutex 實心對 map 的互斥操作 finalObject mutex; // Object on which to synchronize SynchronizedMap(Map<K,V> m) { this.m = Objects.requireNonNull(m); mutex = this; }
如在代碼中看到的,所有對Map的操作都需要用 這個 mutex 來同步,以實現線程安全。比如說下面這些常見的對HashMap的操作方法:
public boolean containsKey(Object key) {synchronized (mutex) {return m.containsKey(key);} } public boolean containsValue(Object value) { synchronized (mutex) {return m.containsValue(value);} } public V get(Object key) { synchronized (mutex) {return m.get(key);} }
除了以上看到的方法之外,其他的Map相關的方法有類似的操作。雖然這個包裝的Map可以實現線程安全的要求,但是,它在多線程環境下的性能表現並不是很好,無論是對Map的讀取還是寫入,偶數需要獲得 mutex 的同步鎖,這會導致所有對Map的安全操作也會進入等待狀態,知道mutex可用。 如果並發級別不高,那麽這個 包裝的Map可以基本滿足要求,但是在搞並發的環境中,我們需要尋找新的解決方案。 ——---> 那就是我們的 ConcurrentHashMap.
二、提高"鎖"性能的策略
1. 減少鎖的持有時間
只在必要時進行同步,減少鎖的持有時間。比如說在一個方法中只有一個變量需要同步,那麽就沒有必要對這整個方法都進行同步,而只需要同步這個變量即可。
// 無謂的加鎖時間 public synchronied void syncMethod() { othrerMethod(); mutexMethod(); otherMethod(); } // 正確的加鎖時間 public void syncMethod() { othrerMethod(); synchronied(this){ mutexMethod(); } otherMethod(); }
2. 減小鎖的粒度
在獲取全局信息方法不頻繁的時候,通過減小鎖的粒度可以搞系統的吞吐量。
3. 讀寫分離鎖替換獨占鎖
在讀都寫少的情況下,使用讀寫分離鎖,多線程讀時不阻塞,而只對寫線程進行同步。
4. 鎖分離
對不同功能的鎖進行不同的鎖策略。
5. 鎖粗化
系統對於"鎖"的調度也是需要性能消耗的,又是我們可以適當的加大鎖的範圍,比如說在循環中盡量減少對鎖的請求和釋放,而是在得到鎖的情況,一次性把問題解決。
線程安全的HashMap