java降低競(jìng)爭(zhēng)鎖的一些方法

本文介紹一下提升并發(fā)可伸縮性的一些方式:減少鎖的持有時(shí)間,降低鎖的粒度卖局,鎖分段斧蜕、避免熱點(diǎn)域以及采用非獨(dú)占的鎖或非阻塞鎖來代替獨(dú)占鎖。

減少鎖的持有時(shí)間

降低發(fā)生競(jìng)爭(zhēng)可能性的一種有效方式就是盡可能縮短鎖的持有時(shí)間砚偶。例如批销,可以將一些與鎖無關(guān)的代碼移出同步代碼塊,尤其是那些開銷較大的操作染坯,以及可能被阻塞的操作均芽,例如I/O操作。

  • 優(yōu)化前
@ThreadSafe
public class AttributeStore {
    @GuardedBy("this") private final Map<String, String>
            attributes = new HashMap<String, String>();

    public synchronized boolean userLocationMatches(String name,
                                                    String regexp) {
        String key = "users." + name + ".location";
        String location = attributes.get(key);
        if (location == null)
            return false;
        else
            return Pattern.matches(regexp, location);
    }
}
  • 優(yōu)化后
@ThreadSafe
public class BetterAttributeStore {
    @GuardedBy("this") private final Map<String, String>
            attributes = new HashMap<String, String>();

    public boolean userLocationMatches(String name, String regexp) {
        String key = "users." + name + ".location";
        String location;
        synchronized (this) {
            location = attributes.get(key);
        }
        if (location == null)
            return false;
        else
            return Pattern.matches(regexp, location);
    }
}

降低鎖的粒度

另一種減小鎖的持有時(shí)間的方式是降低線程請(qǐng)求鎖的頻率(從而減小發(fā)生競(jìng)爭(zhēng)的可能性)单鹿。這可以通過鎖分解和鎖分段等技術(shù)來實(shí)現(xiàn)掀宋,在這些技術(shù)中將采用多個(gè)相互獨(dú)立的鎖來保護(hù)獨(dú)立的狀態(tài)變量,從而改變這些變量在之前由單個(gè)鎖來保護(hù)的情況。這些技術(shù)能減小鎖操作的粒度劲妙,并能實(shí)現(xiàn)更高的可伸縮性湃鹊,然而,使用的鎖越多镣奋,那么發(fā)生死鎖的風(fēng)險(xiǎn)也就越高币呵。

  • 優(yōu)化前
@ThreadSafe
public class ServerStatusBeforeSplit {
    @GuardedBy("this") public final Set<String> users;
    @GuardedBy("this") public final Set<String> queries;

    public ServerStatusBeforeSplit() {
        users = new HashSet<String>();
        queries = new HashSet<String>();
    }

    public synchronized void addUser(String u) {
        users.add(u);
    }

    public synchronized void addQuery(String q) {
        queries.add(q);
    }

    public synchronized void removeUser(String u) {
        users.remove(u);
    }

    public synchronized void removeQuery(String q) {
        queries.remove(q);
    }
}
  • 優(yōu)化后
@ThreadSafe
public class ServerStatusAfterSplit {
    @GuardedBy("users") public final Set<String> users;
    @GuardedBy("queries") public final Set<String> queries;

    public ServerStatusAfterSplit() {
        users = new HashSet<String>();
        queries = new HashSet<String>();
    }

    public void addUser(String u) {
        synchronized (users) {
            users.add(u);
        }
    }

    public void addQuery(String q) {
        synchronized (queries) {
            queries.add(q);
        }
    }

    public void removeUser(String u) {
        synchronized (users) {
            users.remove(u);
        }
    }

    public void removeQuery(String q) {
        synchronized (users) {
            queries.remove(q);
        }
    }
}

鎖分段

在某些情況下,可以將鎖分解技術(shù)進(jìn)一步擴(kuò)展為對(duì)一組獨(dú)立對(duì)象上的鎖進(jìn)行分解侨颈,這種情況被稱為鎖分段余赢。例如,在ConcurrentHashMap的實(shí)現(xiàn)中使用了一個(gè)包含16個(gè)鎖的數(shù)組肛搬,每個(gè)鎖保護(hù)所有散列桶的1/16没佑,其中第N個(gè)散列桶由第(Nmod 16)個(gè)鎖來保護(hù)。假設(shè)散列函數(shù)具有合理的分布性温赔,并且關(guān)鍵字能夠?qū)崿F(xiàn)均勻分布蛤奢,那么這大約能把對(duì)于鎖的請(qǐng)求減少到原來的1/16。正是這項(xiàng)技術(shù)使得ConcurrentHashMap能夠支持多達(dá)16個(gè)并發(fā)的寫入器陶贼。(要使得擁有大量處理器的系統(tǒng)在高訪問量的情況下實(shí)現(xiàn)更高的并發(fā)性啤贩,還可以進(jìn)一步增加鎖的數(shù)量,但僅當(dāng)你能證明并發(fā)寫入線程的競(jìng)爭(zhēng)足夠激烈并需要突破這個(gè)限制時(shí)拜秧,才能將鎖分段的數(shù)量超過默認(rèn)的16個(gè)痹屹。)

鎖分段的一個(gè)劣勢(shì)在于:與采用單個(gè)鎖來實(shí)現(xiàn)獨(dú)占訪問相比,要獲取多個(gè)鎖來實(shí)現(xiàn)獨(dú)占訪問將更加困難并且開銷更高枉氮。通常志衍,在執(zhí)行一個(gè)操作時(shí)最多只需獲取一個(gè)鎖,但在某些情況下需要加鎖整個(gè)容器聊替,例如當(dāng)ConcurrentHashMap需要擴(kuò)展映射范圍楼肪,以及重新計(jì)算鍵值的散列值要分布到更大的桶集合中時(shí),就需要獲取分段所集合中所有的鎖惹悄。

@ThreadSafe
public class StripedMap {
    // Synchronization policy: buckets[n] guarded by locks[n%N_LOCKS]
    private static final int N_LOCKS = 16;
    private final Node[] buckets;
    private final Object[] locks;

    private static class Node {
        Node next;
        Object key;
        Object value;
    }

    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();
    }

    private final int hash(Object key) {
        return Math.abs(key.hashCode() % buckets.length);
    }

    public Object get(Object key) {
        int hash = hash(key);
        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;
    }

    public void clear() {
        for (int i = 0; i < buckets.length; i++) {
            synchronized (locks[i % N_LOCKS]) {
                buckets[i] = null;
            }
        }
    }
}

避免熱點(diǎn)域

如果一個(gè)鎖保護(hù)兩個(gè)獨(dú)立變量X和Y春叫,并且線程A想要訪問X,而線程B想要訪問Y(這類似于在ServerStatus中泣港,一個(gè)線程調(diào)用addUser暂殖,而另一個(gè)線程調(diào)用addQuery),那么這兩個(gè)線程不會(huì)在任何數(shù)據(jù)上發(fā)生競(jìng)爭(zhēng)当纱,即使它們會(huì)在同一個(gè)鎖上發(fā)生競(jìng)爭(zhēng)呛每。當(dāng)每個(gè)操作都請(qǐng)求多個(gè)變量時(shí),鎖的粒度將很難降低坡氯。這是在性能與可伸縮性之間相互制衡的另一個(gè)方面莉给,一些常見的優(yōu)化措施毙石,例如將一些反復(fù)計(jì)算的結(jié)果緩存起來,都會(huì)引入一些“熱點(diǎn)域(HotField)”颓遏,而這些熱點(diǎn)域往往會(huì)限制可伸縮性徐矩。當(dāng)實(shí)現(xiàn)HashMap時(shí),你需要考慮如何在size方法中計(jì)算Map中的元素?cái)?shù)量叁幢。最簡(jiǎn)單的方法就是滤灯,在每次調(diào)用時(shí)都統(tǒng)計(jì)一次元素的數(shù)量。一種常見的優(yōu)化措施是曼玩,在插入和移除元素時(shí)更新一個(gè)計(jì)數(shù)器鳞骤,雖然這在put和remove等方法中略微增加了一些開銷,以確保計(jì)數(shù)器是最新的值黍判,但這將把size方法的開銷從O(n)降低到O(l)豫尽。

在單線程或者采用完全同步的實(shí)現(xiàn)中,使用一個(gè)獨(dú)立的計(jì)數(shù)能很好地提高類似size和isEmpty這些方法的執(zhí)行速度顷帖,但卻導(dǎo)致更難以提升實(shí)現(xiàn)的可伸縮性美旧,因?yàn)槊總€(gè)修改map的操作都需要更新這個(gè)共享的計(jì)數(shù)器。即使使用鎖分段技術(shù)來實(shí)現(xiàn)散列鏈贬墩,那么在對(duì)計(jì)數(shù)器的訪問進(jìn)行同步時(shí)榴嗅,也會(huì)重新導(dǎo)致在使用獨(dú)占鎖時(shí)存在的可伸縮性問題。一個(gè)看似性能優(yōu)化的措施—緩存size操作的結(jié)果陶舞,已經(jīng)變成了一個(gè)可伸縮性問題嗽测。在這種情況下,計(jì)數(shù)器也被稱為熱點(diǎn)域肿孵,因?yàn)槊總€(gè)導(dǎo)致元素?cái)?shù)量發(fā)生變化的操作都需要訪問它唠粥。為了避免這個(gè)問題,ConcurrentHashMap中的size將對(duì)每個(gè)分段進(jìn)行枚舉并將每個(gè)分段中的元素?cái)?shù)量相加停做,而不是維護(hù)一個(gè)全局計(jì)數(shù)厅贪。為了避免枚舉每個(gè)元素,ConcurrentHashMap為每個(gè)分段都維護(hù)了一個(gè)獨(dú)立的計(jì)數(shù)雅宾,并通過每個(gè)分段的鎖來維護(hù)這個(gè)值。

public int size() {
        long n = sumCount();
        return ((n < 0L) ? 0 :
                (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
                (int)n);
    }
final long sumCount() {
        CounterCell[] as = counterCells; CounterCell a;
        long sum = baseCount;
        if (as != null) {
            for (int i = 0; i < as.length; ++i) {
                if ((a = as[i]) != null)
                    sum += a.value;
            }
        }
        return sum;
    }    

代替獨(dú)占鎖

第三種降低競(jìng)爭(zhēng)鎖的影響的技術(shù)就是放棄使用獨(dú)占鎖葵硕,從而有助于使用一種友好并發(fā)的方式來管理共享狀態(tài)眉抬。例如,使用并發(fā)容器懈凹、讀-寫鎖蜀变、不可變對(duì)象以及原子變量。ReadWriteLock能提供比獨(dú)占鎖更高的并發(fā)性介评。而對(duì)于只讀的數(shù)據(jù)結(jié)構(gòu)库北,其中包含的不變性可以完全不需要加鎖操作爬舰。

public class ReadWriteMap <K,V> {
    private final Map<K, V> map;
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private final Lock r = lock.readLock();
    private final Lock w = lock.writeLock();

    public ReadWriteMap(Map<K, V> map) {
        this.map = map;
    }

    public V put(K key, V value) {
        w.lock();
        try {
            return map.put(key, value);
        } finally {
            w.unlock();
        }
    }

    public V remove(Object key) {
        w.lock();
        try {
            return map.remove(key);
        } finally {
            w.unlock();
        }
    }

    public void putAll(Map<? extends K, ? extends V> m) {
        w.lock();
        try {
            map.putAll(m);
        } finally {
            w.unlock();
        }
    }

    public void clear() {
        w.lock();
        try {
            map.clear();
        } finally {
            w.unlock();
        }
    }

    public V get(Object key) {
        r.lock();
        try {
            return map.get(key);
        } finally {
            r.unlock();
        }
    }

    public int size() {
        r.lock();
        try {
            return map.size();
        } finally {
            r.unlock();
        }
    }

    public boolean isEmpty() {
        r.lock();
        try {
            return map.isEmpty();
        } finally {
            r.unlock();
        }
    }

    public boolean containsKey(Object key) {
        r.lock();
        try {
            return map.containsKey(key);
        } finally {
            r.unlock();
        }
    }

    public boolean containsValue(Object value) {
        r.lock();
        try {
            return map.containsValue(value);
        } finally {
            r.unlock();
        }
    }
}

doc

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市寒瓦,隨后出現(xiàn)的幾起案子情屹,更是在濱河造成了極大的恐慌,老刑警劉巖杂腰,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件垃你,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡喂很,警方通過查閱死者的電腦和手機(jī)惜颇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來少辣,“玉大人凌摄,你說我怎么就攤上這事±焖В” “怎么了锨亏?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)煎殷。 經(jīng)常有香客問我屯伞,道長(zhǎng),這世上最難降的妖魔是什么豪直? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任劣摇,我火速辦了婚禮,結(jié)果婚禮上弓乙,老公的妹妹穿的比我還像新娘末融。我一直安慰自己,他們只是感情好暇韧,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布勾习。 她就那樣靜靜地躺著,像睡著了一般懈玻。 火紅的嫁衣襯著肌膚如雪巧婶。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天涂乌,我揣著相機(jī)與錄音艺栈,去河邊找鬼。 笑死湾盒,一個(gè)胖子當(dāng)著我的面吹牛湿右,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播罚勾,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼毅人,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼吭狡!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起丈莺,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤划煮,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后场刑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體般此,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年牵现,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了铐懊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡瞎疼,死狀恐怖科乎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情贼急,我是刑警寧澤茅茂,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站太抓,受9級(jí)特大地震影響空闲,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜走敌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一碴倾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧掉丽,春花似錦跌榔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至项炼,卻和暖如春担平,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背锭部。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來泰國打工暂论, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人空免。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像盆耽,于是被迫代替她去往敵國和親蹋砚。 傳聞我的和親對(duì)象是個(gè)殘疾皇子扼菠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 從三月份找實(shí)習(xí)到現(xiàn)在,面了一些公司坝咐,掛了不少循榆,但最終還是拿到小米、百度墨坚、阿里秧饮、京東、新浪泽篮、CVTE盗尸、樂視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,239評(píng)論 11 349
  • 一個(gè)簡(jiǎn)單的單例示例 單例模式可能是大家經(jīng)常接觸和使用的一個(gè)設(shè)計(jì)模式,你可能會(huì)這么寫 publicclassUnsa...
    Martin說閱讀 2,224評(píng)論 0 6
  • 一.線程安全性 線程安全是建立在對(duì)于對(duì)象狀態(tài)訪問操作進(jìn)行管理帽撑,特別是對(duì)共享的與可變的狀態(tài)的訪問 解釋下上面的話: ...
    黃大大吃不胖閱讀 840評(píng)論 0 3
  • 關(guān)鍵時(shí)刻的決定,決定生死及塘!平時(shí)的選擇莽使,決定關(guān)鍵時(shí)刻的決定的能力!沒讀懂笙僚,返回再讀一遍芳肌。 1、臺(tái)風(fēng)遇難司機(jī) 誰都不知...
    白卷兒閱讀 637評(píng)論 0 1
  • 2017.8.31 今天是8月的最后一天味咳,也是我們特種兵訓(xùn)練第十五天庇勃,明天就是最后一天了,此時(shí)此刻群里的優(yōu)秀的戰(zhàn)友...
    舞悅朵朵閱讀 75評(píng)論 0 0