Java高并發(fā)下鎖的優(yōu)化詳解
簡(jiǎn)述
鎖是最常用的同步方法之一。在高并發(fā)的環(huán)境下,激烈的鎖競(jìng)爭(zhēng)會(huì)導(dǎo)致程序的性能下降。
下面是一些關(guān)于鎖的使用建議,可以把這種副作用降到最低。
減少鎖持有時(shí)間
對(duì)于使用鎖進(jìn)行并發(fā)控制的應(yīng)用程序而言,在鎖競(jìng)爭(zhēng)過(guò)程中,單個(gè)線程對(duì)鎖的持有時(shí)間與系統(tǒng)性能有著直接的關(guān)系。
如果線程持有鎖的時(shí)間很長(zhǎng),那么相對(duì)地,鎖的競(jìng)爭(zhēng)程序也就越激烈。
應(yīng)該盡可能地減少對(duì)某個(gè)鎖的占有時(shí)間,以減少程序間互斥的可能。
public synchronized void syncMethod() { otherCode1(); //無(wú)同步控制需要 needSynMethod(); //有同步控制需要 otherCode2(); //無(wú)同步控制需要 } public void syncMethod2() { otherCode1(); //無(wú)同步控制需要 synchronized(this) { needSynMethod(); //有同步控制需要 } otherCode2(); //無(wú)同步控制需要 }
說(shuō)明:減少鎖的持有時(shí)間有助于降低鎖沖突的可能性,進(jìn)而提升系統(tǒng)的并發(fā)能力。
減少鎖粒度
減少鎖粒度也是一種削弱多線程鎖競(jìng)爭(zhēng)的有效手段。這種技術(shù)典型的使用場(chǎng)景就是ConcurrentHashMap類(lèi)的實(shí)現(xiàn)。ConcurrentHashMap和Hashtable主要區(qū)別就是圍繞著鎖的粒度以及如何鎖,可以簡(jiǎn)單理解成把一個(gè)大的HashTable分解成多個(gè),形成了鎖分離。而Hashtable的實(shí)現(xiàn)方式是—鎖整個(gè)hash表。concurrentHashMap內(nèi)部細(xì)分了若干個(gè)小的hashMap,稱(chēng)之為段(segment),默認(rèn)情況下,被細(xì)分為16個(gè)段。新增的時(shí)候根據(jù)key的hashcode計(jì)算出應(yīng)該存放到哪一個(gè)段中,然后對(duì)這個(gè)段枷鎖,完成put()操作。就是說(shuō),最多可以同時(shí)接收16個(gè)線程同時(shí)插入(前提是16個(gè)不同段插入),從而大大提高吞吐量。但是,減少鎖粒度會(huì)引入一個(gè)新的問(wèn)題,即:當(dāng)系統(tǒng)需要取得全局鎖時(shí),其消耗的資源會(huì)比較多。需要遍歷每一個(gè)段,對(duì)每一個(gè)段進(jìn)行加鎖,最后還要對(duì)每一個(gè)段進(jìn)行解鎖。concurrentHashMap的size()方法會(huì)先使用無(wú)鎖的方式求和,如果失敗才會(huì)嘗試這種加鎖的方法。所以,在高并發(fā)場(chǎng)合,size()的性能差于同步的Hashmap,適用于size()調(diào)用少的場(chǎng)合。 說(shuō)明:所謂減少鎖粒度,就是指縮小鎖定對(duì)象的范圍,從而減少鎖沖突的可能性,進(jìn)行提供系統(tǒng)的并發(fā)能力。
讀寫(xiě)分離鎖來(lái)替換獨(dú)占鎖
ReadWriteLock讀寫(xiě)分離鎖替代獨(dú)占鎖是減少鎖粒度的一種特殊情況。減少鎖粒度是通過(guò)分割數(shù)據(jù)結(jié)構(gòu)來(lái)實(shí)現(xiàn)的,而讀寫(xiě)鎖則是對(duì)系統(tǒng)功能點(diǎn)的分割。 讀操作本身不會(huì)影響數(shù)據(jù)的完整性和一致性。因此,理論上講,在大部分情況下,應(yīng)該可以允許多想成同時(shí)讀。 讀寫(xiě)鎖的訪問(wèn)約束情況:讀-讀(非阻塞)、讀-寫(xiě)(阻塞)、寫(xiě)-讀(阻塞)、寫(xiě)-寫(xiě)(阻塞)
public class ReadWriteLockTest2 { public static void main(String[] args) { //創(chuàng)建一個(gè)鎖對(duì)象 ReadWriteLock lock = new ReentrantReadWriteLock(false); //創(chuàng)建一個(gè)線程池 ExecutorService pool = Executors.newFixedThreadPool(2); //創(chuàng)建一些并發(fā)訪問(wèn) RWRun rw1 = new RWRun(lock,true); RWRun rw2 = new RWRun(lock,true); RWRun rw3 = new RWRun(lock,false); //在線程池中執(zhí)行各個(gè)的操作 pool.execute(rw1); pool.execute(rw2); pool.execute(rw3); //關(guān)閉線程池 pool.shutdown(); } } class RWRun implements Runnable { private ReadWriteLock myLock; //執(zhí)行操作所需的鎖對(duì)象 private boolean ischeck; //是否查詢 public RWRun(ReadWriteLock myLock, boolean ischeck) { super(); this.myLock = myLock; this.ischeck = ischeck; } public void run() { if (ischeck) { //獲取讀鎖 myLock.readLock().lock(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //釋放讀鎖 myLock.readLock().unlock(); } else { //獲取寫(xiě)鎖 myLock.writeLock().lock(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //釋放寫(xiě)鎖 myLock.writeLock().unlock(); } } }
上面執(zhí)行完的時(shí)間為2秒鐘,如果是獨(dú)占鎖 就需要3秒鐘 結(jié)論:在讀多寫(xiě)少的場(chǎng)合,使用讀寫(xiě)鎖可以有效提升系統(tǒng)的并發(fā)能力。
鎖分離
在LinkedBlockingQueue的實(shí)現(xiàn)中,take()和put()分別實(shí)現(xiàn)了從隊(duì)列中取得數(shù)據(jù)和往隊(duì)列中增加數(shù)據(jù)的功能。
雖然兩個(gè)函數(shù)都對(duì)當(dāng)前隊(duì)列進(jìn)行了修改操作,但由于是基于鏈表的,因此,兩個(gè)操作分別作用于隊(duì)列的前端和尾端,從理論上來(lái)說(shuō),兩者并不沖突。
所以在JDK中,采用了兩把不同的鎖,分離了toke()和put()的操作,實(shí)現(xiàn)了可并發(fā)的操作。
//take()函數(shù)需要持有的鎖 private final ReentrantLock takeLock = new ReentrantLock(); private final Condition notEmpty = takeLock.newCondition(); //put()函數(shù)需要持有的鎖 private final ReentrantLock putLock = new ReentrantLock(); private final Condition notFull = putLock.newCondition();
鎖粗化
虛擬機(jī)在遇到一連串連續(xù)地對(duì)同一鎖不斷進(jìn)行請(qǐng)求和釋放的操作時(shí),便會(huì)把所有的鎖操作整合成對(duì)鎖的一次請(qǐng)求,從而減少對(duì)鎖的請(qǐng)求同步次數(shù),這個(gè)操作叫做鎖的粗化。
例子:在循環(huán)內(nèi)請(qǐng)求鎖時(shí)。
for (int i =0 i < n; i++) { synchronized (lock) { doSomething(); } }
更加合理的做法應(yīng)該是在外層只請(qǐng)求一次鎖
到此這篇關(guān)于Java高并發(fā)下鎖的優(yōu)化詳解的文章就介紹到這了,更多相關(guān)高并發(fā)下鎖的優(yōu)化內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
maven deploy時(shí)報(bào)錯(cuò)的解決方法
這篇文章主要介紹了maven deploy時(shí)報(bào)錯(cuò)的解決方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09Java中HashMap和TreeMap的區(qū)別深入理解
首先介紹一下什么是Map。在數(shù)組中我們是通過(guò)數(shù)組下標(biāo)來(lái)對(duì)其內(nèi)容索引的,而在Map中我們通過(guò)對(duì)象來(lái)對(duì)對(duì)象進(jìn)行索引,用來(lái)索引的對(duì)象叫做key,其對(duì)應(yīng)的對(duì)象叫做value2012-12-12MyBatis-Plus中使用EntityWrappe進(jìn)行列表數(shù)據(jù)倒序設(shè)置方式
這篇文章主要介紹了MyBatis-Plus中使用EntityWrappe進(jìn)行列表數(shù)據(jù)倒序設(shè)置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03Java實(shí)現(xiàn)將數(shù)據(jù)導(dǎo)出為Word文檔的方法步驟
我們?cè)陂_(kāi)發(fā)一些系統(tǒng)的時(shí)候,例如OA系統(tǒng),經(jīng)常能遇到將審批單數(shù)據(jù)導(dǎo)出為word和excel文檔的需求,導(dǎo)出為excel是比較簡(jiǎn)單的,但是word文檔的格式不像表格那樣可以輕松的定位,所以本文給大家介紹了Java怎樣實(shí)現(xiàn)將數(shù)據(jù)導(dǎo)出為Word文檔,需要的朋友可以參考下2025-01-01SpringMVC中controller接收json數(shù)據(jù)的方法
這篇文章主要為大家詳細(xì)介紹了SpringMVC中controller接收json數(shù)據(jù)的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-09-09詳解Java Bellman-Ford算法原理及實(shí)現(xiàn)
Bellman-Ford算法與Dijkstra算法類(lèi)似,都是以松弛操作作為基礎(chǔ),Bellman-Ford算法是對(duì)所有邊都進(jìn)行松弛操作,本文將詳解Bellman-Ford算法原理及實(shí)現(xiàn),感興趣的可以了解一下2022-07-07