摘要:減少鎖的持有時(shí)間降低發(fā)生競爭可能性的一種有效方式就是盡可能縮短鎖的持有時(shí)間。代替獨(dú)占鎖第三種降低競爭鎖的影響的技術(shù)就是放棄使用獨(dú)占鎖,從而有助于使用一種友好并發(fā)的方式來管理共享狀態(tài)。
序
本文介紹一下提升并發(fā)可伸縮性的一些方式:減少鎖的持有時(shí)間,降低鎖的粒度,鎖分段、避免熱點(diǎn)域以及采用非獨(dú)占的鎖或非阻塞鎖來代替獨(dú)占鎖。
減少鎖的持有時(shí)間降低發(fā)生競爭可能性的一種有效方式就是盡可能縮短鎖的持有時(shí)間。例如,可以將一些與鎖無關(guān)的代碼移出同步代碼塊,尤其是那些開銷較大的操作,以及可能被阻塞的操作,例如I/O操作。
優(yōu)化前
@ThreadSafe public class AttributeStore { @GuardedBy("this") private final Mapattributes = new HashMap (); 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降低鎖的粒度attributes = new HashMap (); 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ā)生競爭的可能性)。這可以通過鎖分解和鎖分段等技術(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 Setusers; @GuardedBy("this") public final Set queries; public ServerStatusBeforeSplit() { users = new HashSet (); queries = new HashSet (); } 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鎖分段users; @GuardedBy("queries") public final Set queries; public ServerStatusAfterSplit() { users = new HashSet (); queries = new HashSet (); } 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ā)寫入線程的競爭足夠激烈并需要突破這個(gè)限制時(shí),才能將鎖分段的數(shù)量超過默認(rèn)的16個(gè)。)
鎖分段的一個(gè)劣勢在于:與采用單個(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ā)生競爭,即使它們會(huì)在同一個(gè)鎖上發(fā)生競爭。當(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ù)量。最簡單的方法就是,在每次調(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ú)占鎖
第三種降低競爭鎖的影響的技術(shù)就是放棄使用獨(dú)占鎖,從而有助于使用一種友好并發(fā)的方式來管理共享狀態(tài)。例如,使用并發(fā)容器、讀-寫鎖、不可變對(duì)象以及原子變量。ReadWriteLock能提供比獨(dú)占鎖更高的并發(fā)性。而對(duì)于只讀的數(shù)據(jù)結(jié)構(gòu),其中包含的不變性可以完全不需要加鎖操作。
public class ReadWriteMapdoc{ private final Map map; private final ReadWriteLock lock = new ReentrantReadWriteLock(); private final Lock r = lock.readLock(); private final Lock w = lock.writeLock(); public ReadWriteMap(Map 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(); } } }
Java并發(fā)編程實(shí)戰(zhàn)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/70428.html
摘要:有可能,會(huì)造成優(yōu)先級(jí)反轉(zhuǎn)或者饑餓現(xiàn)象。悲觀鎖在中的使用,就是利用各種鎖。對(duì)于而言,其是獨(dú)享鎖。偏向鎖,顧名思義,它會(huì)偏向于第一個(gè)訪問鎖的線程,大多數(shù)情況下鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得。 理解鎖的基礎(chǔ)知識(shí) 如果想要透徹的理解java鎖的來龍去脈,需要先了解以下基礎(chǔ)知識(shí)。 基礎(chǔ)知識(shí)之一:鎖的類型 按照其性質(zhì)分類 公平鎖/非公平鎖 公平鎖是指多個(gè)線程按照申請(qǐng)鎖的順序來獲...
摘要:關(guān)于降低鎖的競爭程度從奶爸的角度思考題外話這篇文章的靈感來源于近日帶娃耍。具體可參考定律,大致可理解為處理器的利用率與處理器數(shù)量和串行比例成反比,此外,在鎖上發(fā)生競爭,導(dǎo)致上下文切換的開銷增加,進(jìn)而降低程序的性能。 關(guān)于降低鎖的競爭程度------從奶爸的角度思考 題外話:這篇文章的靈感來源于近日帶娃耍。 鎖競爭帶來的問題 在鎖上發(fā)生競爭,導(dǎo)致串行操作花費(fèi)的時(shí)間比例增加,進(jìn)而降低程序...
摘要:公平鎖非公平鎖公平鎖公平鎖是指多個(gè)線程按照申請(qǐng)鎖的順序來獲取鎖。加鎖后,任何其他試圖再次加鎖的線程會(huì)被阻塞,直到當(dāng)前進(jìn)程解鎖。重量級(jí)鎖會(huì)讓其他申請(qǐng)的線程進(jìn)入阻塞,性能降低。 Java 中15種鎖的介紹 在讀很多并發(fā)文章中,會(huì)提及各種各樣鎖如公平鎖,樂觀鎖等等,這篇文章介紹各種鎖的分類。介紹的內(nèi)容如下: 公平鎖 / 非公平鎖 可重入鎖 / 不可重入鎖 獨(dú)享鎖 / 共享鎖 互斥鎖 / 讀...
摘要:如果同一個(gè)線程再次請(qǐng)求該鎖,計(jì)數(shù)器會(huì)遞增,每次占有的線程退出同步代碼塊時(shí)計(jì)數(shù)器會(huì)遞減,直至減為時(shí)鎖才會(huì)被釋放。表示或在該上的所有線程的個(gè)數(shù)用來實(shí)現(xiàn)重入鎖的計(jì)數(shù)。只有兩種可能的值表示沒有需要喚醒的線程表示要喚醒一個(gè)繼任線程來競爭鎖。 一、synchronized 1.類型 (1)對(duì)象鎖 對(duì)象鎖是作用在實(shí)例方法或者一個(gè)對(duì)象實(shí)例上面的 一個(gè)類可以有多個(gè)實(shí)例對(duì)象,因此一個(gè)類的對(duì)象鎖可能會(huì)有多個(gè)...
摘要:一般情況下,可以從兩個(gè)角度進(jìn)行鎖優(yōu)化對(duì)單個(gè)鎖算法的優(yōu)化和對(duì)鎖粒度的細(xì)分。單個(gè)鎖的優(yōu)化自旋鎖非自旋鎖在未獲取鎖的情況會(huì)被阻塞,之后再喚醒嘗試獲得鎖。 Java鎖優(yōu)化 應(yīng)用程序在并發(fā)環(huán)境下會(huì)產(chǎn)生很多問題,通常情況下,我們可以通過加鎖來解決多線程對(duì)臨界資源的訪問問題。但是加鎖往往會(huì)成為系統(tǒng)的瓶頸,因?yàn)榧渔i和釋放鎖會(huì)涉及到與操作系統(tǒng)的交互,會(huì)有很大的性能問題。那么這個(gè)時(shí)候基于鎖的優(yōu)化手段就顯得...
閱讀 3401·2021-10-08 10:15
閱讀 5449·2021-09-23 11:56
閱讀 1467·2019-08-30 15:55
閱讀 445·2019-08-29 16:05
閱讀 2725·2019-08-29 12:34
閱讀 2036·2019-08-29 12:18
閱讀 915·2019-08-26 12:02
閱讀 1650·2019-08-26 12:00