提到鎖畔乙,大家可能都會想到synchronized關(guān)鍵字君仆,使用它的確可以解決一切并發(fā)問題,但是對于系統(tǒng)吞吐要求更高的牲距,在這里提供了幾個(gè)小技巧返咱,幫助大家減小鎖粒度,提高系統(tǒng)并發(fā)能力牍鞠。
初級技巧 - 樂觀鎖
樂觀鎖適合這樣的場景:讀不會沖突咖摹,寫會沖突。同時(shí)讀的頻率遠(yuǎn)大于寫难述。
以下面的代碼為例萤晴,悲觀鎖的實(shí)現(xiàn):
public Object get(Object key) {
synchronized(map) {
if(map.get(key) == null) {
// set some values
}
return map.get(key);
}
}
樂觀鎖的實(shí)現(xiàn):
public Object get(Object key) {
Object val = null;
if((val = map.get(key) == null) {
// 當(dāng)map取值為null時(shí)再加鎖判斷
synchronized (map) {
if(val = map.get(key) == **null**) {
// set some value to map...
}
}
}
return map.get(key);
}
中級技巧 - String.intern()
樂觀鎖不能很好解決大量寫沖突問題,但是如果很多場景下胁后,鎖實(shí)際上只是針對某個(gè)用戶或者某個(gè)訂單店读。比如一個(gè)用戶必須先創(chuàng)建session,才能進(jìn)行后面的操作攀芯。但是由于網(wǎng)絡(luò)原因屯断,創(chuàng)建用戶session的請求和后續(xù)請求幾乎同時(shí)達(dá)到,而并行線程可能會先處理后續(xù)請求。一般情況殖演,需要對用戶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);
這個(gè)比較類似于數(shù)據(jù)庫表鎖和行鎖的概念,顯然行鎖的并發(fā)能力比表鎖高很多朋鞍。
使用String.inter()是這種思路的一種具體實(shí)現(xiàn)已添。類 String 維護(hù)一個(gè)字符串池。 當(dāng)調(diào)用 intern 方法時(shí)滥酥,如果池已經(jīng)包含一個(gè)等于此 String 對象的字符串(該對象由 equals(Object) 方法確定)更舞,則返回池中的字符串】参牵可見缆蝉,當(dāng)String相同時(shí),String.intern()總是返回同一個(gè)對象瘦真,因此就實(shí)現(xiàn)了對同一用戶加鎖刊头。由于鎖的粒度局限于具體用戶,使系統(tǒng)獲得了最大程度的并發(fā)诸尽。
public void doSomeThing(String uid) {
synchronized (uid.intern()) {
// ...
}
}
CopyOnWriteMap原杂?
既然說到了“類似于數(shù)據(jù)庫中的行鎖的概念”,就不得不提一下MVCC您机,Java中CopyOnWrite類實(shí)現(xiàn)了MVCC穿肄。Copy On Write是這樣一種機(jī)制。當(dāng)我們讀取共享數(shù)據(jù)的時(shí)候际看,直接讀取咸产,不需要同步。當(dāng)我們修改數(shù)據(jù)的時(shí)候仲闽,我們就把當(dāng)前數(shù)據(jù)Copy一份副本脑溢,然后在這個(gè)副本 上進(jìn)行修改,完成之后赖欣,再用修改后的副本屑彻,替換掉原來的數(shù)據(jù)。這種方法就叫做Copy On Write畏鼓。
但是酱酬,壶谒,云矫,JDK并沒有提供CopyOnWriteMap,為什么汗菜?下面有個(gè)很好的回答让禀,那就是已經(jīng)有了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 維護(hù)一個(gè)字符串池是放在JVM perm區(qū)的巡揍,如果用戶數(shù)特別多痛阻,導(dǎo)致放入字符串池的String不可控,有可能導(dǎo)致OOM錯(cuò)誤或者過多的Full GC腮敌。怎么樣能控制鎖的個(gè)數(shù)阱当,同時(shí)減小粒度鎖呢?直接使用Java ConcurrentHashMap糜工?或者你想加入自己更精細(xì)的控制弊添?那么可以借鑒ConcurrentHashMap的方式,將需要加鎖的對象分為多個(gè)bucket捌木,每個(gè)bucket加一個(gè)鎖油坝,偽代碼如下:
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
}
}