初級技巧 - 樂觀鎖
樂觀鎖適合這樣的場景:讀不會衝突,寫會衝突。同時讀的頻率遠大於寫。
以下面的代碼爲例,悲觀鎖的實現:
Java代碼
public Object get(Object key) {
synchronized(map) {
if(map.get(key) == null) {
// set some values
}
return map.get(key);
}
}
樂觀鎖的實現:
Java代碼
public Object get(Object key) {
Object val = null;
if((val = map.get(key) == null) {
// 當map取值爲null時再加鎖判斷
synchronized(map) {
if(val = map.get(key) == null) {
// set some value to map...
}
}
}
return map.get(key);
}
中級技巧 - String.intern()
樂觀鎖不能很好解決大量寫衝突問題,但是如果很多場景下,鎖實際上只是針對某個用戶或者某個訂單。比如一個用戶必須先創建session,才能進行後面的操作。但是由於網絡原因,創建用戶session的請求和後續請求幾乎同時達到,而並行線程可能會先處理後續請求。一般情況,需要對用戶sessionMap加鎖,比如上面的樂觀鎖。在這種場景下,可以講鎖限定到用戶本身上,即從原來的
lock.lock();
int num=storage.get(key);
storage.set(key,num+1);
lock.unlock();
更改爲:
lock.lock(key);
int num=storage.get(key);
storage.set(key,num+1);
lock.unlock(key);
這個比較類似於數據庫表鎖和行鎖的概念,顯然行鎖的併發能力比表鎖高很多。
使用String.inter()是這種思路的一種具體實現。類 String 維護一個字符串池。 當調用 intern 方法時,如果池已經包含一個等於此 String 對象的字符串(該對象由 equals(Object) 方法確定),則返回池中的字符串。可見,當String相同時,String.intern()總是返回同一個對象,因此就實現了對同一用戶加鎖。由於鎖的粒度侷限於具體用戶,使系統獲得了最大程度的併發。
Java代碼
public void doSomeThing(String uid) {
synchronized(uid.intern()) {
// ...
}
}
CopyOnWriteMap?
既然說到了“類似於數據庫中的行鎖的概念”,就不得不提一下MVCC,Java中CopyOnWrite類實現了MVCC。Copy On Write是這樣一種機制。當我們讀取共享數據的時候,直接讀取,不需要同步。當我們修改數據的時候,我們就把當前數據Copy一份副本,然後在這個副本 上進行修改,完成之後,再用修改後的副本,替換掉原來的數據。這種方法就叫做Copy On Write。
但是,,,JDK並沒有提供CopyOnWriteMap,爲什麼?下面有個很好的回答,那就是已經有了ConcurrentHashMap,爲什麼還需要CopyOnWriteMap?
Fredrik Bromee 寫道
I guess this depends on your use case, but why would you need a CopyOnWriteMap when you already have a ConcurrentHashMap?
For a plain lookup table with many readers and only one or few updates it is a good fit.
Compared to a copy on write collection:
Read concurrency:
Equal to a copy on write collection. Several readers can retrieve elements from the map concurrently in a lock-free fashion.
Write concurrency:
Better concurrency than the copy on write collections that basically serialize updates (one update at a time). Using a concurrent hash map you have a good chance of doing several updates concurrently. If your hash keys are evenly distributed.
If you do want to have the effect of a copy on write map, you can always initialize a ConcurrentHashMap with a concurrency level of 1.
高級技巧 - 類ConcurrentHashMap
String.inter()的缺陷是類 String 維護一個字符串池是放在JVM perm區的,如果用戶數特別多,導致放入字符串池的String不可控,有可能導致OOM錯誤或者過多的Full GC。怎麼樣能控制鎖的個數,同時減小粒度鎖呢?直接使用Java ConcurrentHashMap?或者你想加入自己更精細的控制?那麼可以借鑑ConcurrentHashMap的方式,將需要加鎖的對象分爲多個bucket,每個bucket加一個鎖,僞代碼如下:
Java代碼
Map locks = new Map();
List lockKeys = new List();
for(int number : 1 - 10000) {
Object lockKey = new Object();
lockKeys.add(lockKey);
locks.put(lockKey, new Object());
}
public void doSomeThing(String uid) {
Object lockKey = lockKeys.get(uid.hash() % lockKeys.size());
Object lock = locks.get(lockKey);
synchronized(lock) {
// do something
}
}