Java多線程并發(fā)編程和鎖原理解析
這篇文章主要介紹了Java多線程并發(fā)編程和鎖原理解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
一.前言
最近項(xiàng)目遇到多線程并發(fā)的情景(并發(fā)搶單&恢復(fù)庫(kù)存并行),代碼在正常情況下運(yùn)行沒(méi)有什么問(wèn)題,在高并發(fā)壓測(cè)下會(huì)出現(xiàn):庫(kù)存超發(fā)/總庫(kù)存與sku庫(kù)存對(duì)不上等各種問(wèn)題。
在運(yùn)用了 限流/加鎖等方案后,問(wèn)題得到解決。
加鎖方案見(jiàn)下文。
二.樂(lè)觀鎖 & 悲觀鎖
1.樂(lè)觀鎖
顧名思義,就是很樂(lè)觀,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人不會(huì)修改,所以不會(huì)上鎖,但是在更新的時(shí)候會(huì)判斷一下在此期間別人有沒(méi)有去更新這個(gè)數(shù)據(jù),可以使用版本號(hào)(version)等機(jī)制。
例如:mybatis-plus 自帶插件OptimisticLockerInterceptor,在數(shù)據(jù)庫(kù)表加上一個(gè)version字段,每次更新完數(shù)據(jù)庫(kù)mp會(huì)自動(dòng)在version字段上加1,如果在更新提交的時(shí)候發(fā)現(xiàn)version字段的值與數(shù)據(jù)庫(kù)中最新的值不一致,則提交失敗。
2.悲觀鎖
悲觀鎖總是假設(shè)會(huì)出現(xiàn)最壞的情況,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人會(huì)修改,所以每次在拿數(shù)據(jù)的時(shí)候都會(huì)上鎖,這樣別人想拿這個(gè)數(shù)據(jù)就會(huì)阻塞直到它拿到鎖。
傳統(tǒng)的MySQL關(guān)系型數(shù)據(jù)庫(kù)里邊就用到了很多這種鎖機(jī)制,比如行鎖,表鎖等,讀鎖,寫(xiě)鎖等,都是在做操作之前先上鎖。
Java的同步synchronized關(guān)鍵字的實(shí)現(xiàn)就是典型的悲觀鎖。
3.總結(jié)
悲觀鎖適合寫(xiě)操作多的場(chǎng)景,先加鎖可以保證寫(xiě)操作時(shí)數(shù)據(jù)正確。
樂(lè)觀鎖適合讀操作多的場(chǎng)景,不加鎖的特點(diǎn)能夠使其讀操作的性能大幅提升。
三.獨(dú)占鎖 & 共享鎖
1.獨(dú)占鎖
是指該鎖一次只能被一個(gè)線程所持有。
例如:Synchronized 和 ReentrantLock ,它們同時(shí)也是悲觀鎖
2.共享鎖
是指該鎖可被多個(gè)線程所持有,允許多個(gè)線程同時(shí)去獲取。
例如:Semaphore 和 ReadWriteLock 和 countdownlatch,其讀鎖是共享鎖,寫(xiě)鎖是獨(dú)享鎖。
3.總結(jié)
讀鎖的共享鎖可保證并發(fā)讀是非常高效的,讀寫(xiě),寫(xiě)讀 ,寫(xiě)寫(xiě)的過(guò)程是互斥的。
獨(dú)享鎖與共享鎖也是通過(guò)AQS來(lái)實(shí)現(xiàn)的,通過(guò)實(shí)現(xiàn)不同的方法,來(lái)實(shí)現(xiàn)獨(dú)享或者共享。
四.公平鎖 & 非公平鎖
1.公平鎖
加鎖前先查看是否有排隊(duì)等待的線程,有的話優(yōu)先處理排在前面的線程,先來(lái)先得。
2.非公平鎖
線程加鎖時(shí)直接嘗試獲取鎖,獲取不到就自動(dòng)到隊(duì)尾等待。
3.總結(jié)
更多的是直接使用非公平鎖:非公平鎖比公平鎖性能高5-10倍,因?yàn)楣芥i需要在多核情況下維護(hù)一個(gè)隊(duì)列,如果當(dāng)前線程不是隊(duì)列的第一個(gè)無(wú)法獲取鎖,增加了線程切換次數(shù)。
五.java線程鎖
由于多個(gè)線程是共同占有所屬進(jìn)程的資源和地址空間的,那么就會(huì)存在一個(gè)問(wèn)題:如果多個(gè)線程要同時(shí)訪問(wèn)某個(gè)資源,怎么處理?
在Java并發(fā)編程中,經(jīng)常遇到多個(gè)線程訪問(wèn)同一個(gè)共享資源 ,這時(shí)候作為開(kāi)發(fā)者必須考慮如何維護(hù)數(shù)據(jù)一致性,這就是Java鎖機(jī)制(同步問(wèn)題)的來(lái)源。
Java提供了多種多線程鎖機(jī)制的實(shí)現(xiàn)方式,常見(jiàn)的有:
- synchronized
- ReentrantLock
- Semaphore
- AtomicInteger等
每種機(jī)制都有優(yōu)缺點(diǎn)與各自的適用場(chǎng)景,必須熟練掌握他們的特點(diǎn)才能在Java多線程應(yīng)用開(kāi)發(fā)時(shí)得心應(yīng)手。
1.Synchronized
在Java中synchronized關(guān)鍵字被常用于維護(hù)數(shù)據(jù)一致性。synchronized機(jī)制是給共享資源上鎖,只有拿到鎖的線程才可以訪問(wèn)共享資源,這樣就可以強(qiáng)制使得對(duì)共享資源的訪問(wèn)都是順序的。
Java開(kāi)發(fā)人員都認(rèn)識(shí)synchronized,使用它來(lái)實(shí)現(xiàn)多線程的同步操作是非常簡(jiǎn)單的,只要在需要同步的對(duì)方的方法、類或代碼塊中加入該關(guān)鍵字,它能夠保證在同一個(gè)時(shí)刻最多只有一個(gè)線程執(zhí)行同一個(gè)對(duì)象的同步代碼,可保證修飾的代碼在執(zhí)行過(guò)程中不會(huì)被其他線程干擾。使用synchronized修飾的代碼具有原子性和可見(jiàn)性,在需要進(jìn)程同步的程序中使用的頻率非常高,可以滿足一般的進(jìn)程同步要求。
synchronized (obj) { //方法 ……. }
synchronized實(shí)現(xiàn)的機(jī)理依賴于軟件層面上的JVM,因此其性能會(huì)隨著Java版本的不斷升級(jí)而提高。
到了Java1.6,synchronized進(jìn)行了很多的優(yōu)化,有適應(yīng)自旋、鎖消除、鎖粗化、輕量級(jí)鎖及偏向鎖等,效率有了本質(zhì)上的提高。在之后推出的Java1.7與1.8中,均對(duì)該關(guān)鍵字的實(shí)現(xiàn)機(jī)理做了優(yōu)化。
需要說(shuō)明的是,當(dāng)線程通過(guò)synchronized等待鎖時(shí)是不能被Thread.interrupt()中斷的,因此程序設(shè)計(jì)時(shí)必須檢查確保合理,否則可能會(huì)造成線程死鎖的尷尬境地。
最后,盡管Java實(shí)現(xiàn)的鎖機(jī)制有很多種,并且有些鎖機(jī)制性能也比synchronized高,但還是強(qiáng)烈推薦在多線程應(yīng)用程序中使用該關(guān)鍵字,因?yàn)閷?shí)現(xiàn)方便,后續(xù)工作由JVM來(lái)完成,可靠性高。只有在確定鎖機(jī)制是當(dāng)前多線程程序的性能瓶頸時(shí),才考慮使用其他機(jī)制,如ReentrantLock等。
總結(jié):在資源競(jìng)爭(zhēng)不是很激烈的情況下,偶爾會(huì)有同步的情形下,synchronized是很合適的。原因在于,編譯程序通常會(huì)盡可能的進(jìn)行優(yōu)化synchronize,另外可讀性非常好。
2.ReentrantLock
可重入鎖,顧名思義,這個(gè)鎖可以被線程多次重復(fù)進(jìn)入進(jìn)行獲取操作。
ReentantLock繼承接口Lock并實(shí)現(xiàn)了接口中定義的方法,除了能完成synchronized所能完成的所有工作外,還提供了諸如可響應(yīng)中斷鎖、可輪詢鎖請(qǐng)求、定時(shí)鎖等避免多線程死鎖的法。
Lock實(shí)現(xiàn)的機(jī)理依賴于特殊的CPU指定,可以認(rèn)為不受JVM的約束,并可以通過(guò)其他語(yǔ)言平臺(tái)來(lái)完成底層的實(shí)現(xiàn)。在并發(fā)量較小的多線程應(yīng)用程序中,ReentrantLock與synchronized性能相差無(wú)幾,但在高并發(fā)量的條件下,synchronized性能會(huì)迅速下降幾十倍,而ReentrantLock的性能卻能依然維持一個(gè)水準(zhǔn)。
因此我們建議在高并發(fā)量情況下使用ReentrantLock。
ReentrantLock引入兩個(gè)概念:公平鎖與非公平鎖。
公平鎖指的是鎖的分配機(jī)制是公平的,通常先對(duì)鎖提出獲取請(qǐng)求的線程會(huì)先被分配到鎖。反之,JVM按隨機(jī)、就近原則分配鎖的機(jī)制則稱為不公平鎖。
ReentrantLock在構(gòu)造函數(shù)中提供了是否公平鎖的初始化方式,默認(rèn)為非公平鎖。這是因?yàn)?,非公平鎖實(shí)際執(zhí)行的效率要遠(yuǎn)遠(yuǎn)超出公平鎖,除非程序有特殊需要,否則最常用非公平鎖的分配機(jī)制。
ReentrantLock通過(guò)方法lock()與unlock()來(lái)進(jìn)行加鎖與解鎖操作,與synchronized會(huì)被JVM自動(dòng)解鎖機(jī)制不同,ReentrantLock加鎖后需要手動(dòng)進(jìn)行解鎖。為了避免程序出現(xiàn)異常而無(wú)法正常解鎖的情況,使用ReentrantLock必須在finally控制塊中進(jìn)行解鎖操作。通常使用方式如下所示:
/** * 初始化一個(gè)非公平鎖(該鎖只適用于單實(shí)例) */ private static Lock lock = new ReentrantLock(false); void test() { try { lock.lock(); //...執(zhí)行業(yè)務(wù)邏輯 }finally { lock.unlock(); } }
總結(jié):在資源競(jìng)爭(zhēng)不激烈的情形下,性能稍微比synchronized差點(diǎn)點(diǎn)。但是當(dāng)同步非常激烈的時(shí)候,synchronized的性能一下子能下降好幾十倍,而ReentrantLock確還能維持常態(tài)。高并發(fā)量情況下使用ReentrantLock。
注意:Spring注解和同步鎖不能同步問(wèn)題解決
3.Semaphore (信號(hào)量)
上述兩種鎖機(jī)制類型都是“互斥鎖”,互斥是進(jìn)程同步關(guān)系的一種特殊情況,相當(dāng)于只存在一個(gè)臨界資源,因此同時(shí)最多只能給一個(gè)線程提供服務(wù)。但是,在實(shí)際復(fù)雜的多線程應(yīng)用程序中,可能存在多個(gè)臨界資源,這時(shí)候我們可以借助Semaphore信號(hào)量來(lái)完成多個(gè)臨界資源的訪問(wèn)。
Semaphore基本能完成ReentrantLock的所有工作,使用方法也與之類似,通過(guò)acquire()與release()方法來(lái)獲得和釋放臨界資源。
經(jīng)實(shí)測(cè),Semaphone.acquire()方法默認(rèn)為可響應(yīng)中斷鎖,與ReentrantLock.lockInterruptibly()作用效果一致,也就是說(shuō)在等待臨界資源的過(guò)程中可以被Thread.interrupt()方法中斷。
此外,Semaphore也實(shí)現(xiàn)了可輪詢的鎖請(qǐng)求與定時(shí)鎖的功能,除了方法名tryAcquire與tryLock不同,其使用方法與ReentrantLock幾乎一致。Semaphore也提供了公平與非公平鎖的機(jī)制,也可在構(gòu)造函數(shù)中進(jìn)行設(shè)定。
Semaphore的鎖釋放操作也由手動(dòng)進(jìn)行,因此與ReentrantLock一樣,為避免線程因拋出異常而無(wú)法正常釋放鎖的情況發(fā)生,釋放鎖的操作也必須在finally代碼塊中完成。
/** 定義一個(gè)非公平的共享鎖 */ public static Semaphore LOCK = new Semaphore(5, false); void test() { try{ //獲取許可 LOCK.acquire(); } finally { //釋放許可 LOCK.release(); } }
總結(jié):Semaphore有著非常強(qiáng)大的功能,并且是共享鎖,在特殊情景時(shí)非常有效。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持我們。
上一篇:Java緩存Map設(shè)置過(guò)期時(shí)間實(shí)現(xiàn)解析
欄 目:Java
下一篇:SPRINGBOOT讀取PROPERTIES配置文件數(shù)據(jù)過(guò)程詳解
本文標(biāo)題:Java多線程并發(fā)編程和鎖原理解析
本文地址:http://mengdiqiu.com.cn/a1/Java/8910.html
您可能感興趣的文章
- 01-10Java實(shí)現(xiàn)動(dòng)態(tài)模擬時(shí)鐘
- 01-10利用Java實(shí)現(xiàn)復(fù)制Excel工作表功能
- 01-10JavaWeb實(shí)現(xiàn)郵件發(fā)送功能
- 01-10java基于poi導(dǎo)出excel透視表代碼實(shí)例
- 01-10Java實(shí)現(xiàn)動(dòng)態(tài)數(shù)字時(shí)鐘
- 01-10基于Java驗(yàn)證jwt token代碼實(shí)例
- 01-10java實(shí)現(xiàn)液晶數(shù)字字體顯示當(dāng)前時(shí)間
- 01-10淺談Java中真的只有值傳遞么
- 01-10Java動(dòng)態(tài)顯示當(dāng)前日期和時(shí)間
- 01-10如何解決線程太多導(dǎo)致java socket連接池出現(xiàn)的問(wèn)題


閱讀排行
- 1C語(yǔ)言 while語(yǔ)句的用法詳解
- 2java 實(shí)現(xiàn)簡(jiǎn)單圣誕樹(shù)的示例代碼(圣誕
- 3利用C語(yǔ)言實(shí)現(xiàn)“百馬百擔(dān)”問(wèn)題方法
- 4C語(yǔ)言中計(jì)算正弦的相關(guān)函數(shù)總結(jié)
- 5c語(yǔ)言計(jì)算三角形面積代碼
- 6什么是 WSH(腳本宿主)的詳細(xì)解釋
- 7C++ 中隨機(jī)函數(shù)random函數(shù)的使用方法
- 8正則表達(dá)式匹配各種特殊字符
- 9C語(yǔ)言十進(jìn)制轉(zhuǎn)二進(jìn)制代碼實(shí)例
- 10C語(yǔ)言查找數(shù)組里數(shù)字重復(fù)次數(shù)的方法
本欄相關(guān)
- 01-10Java實(shí)現(xiàn)動(dòng)態(tài)模擬時(shí)鐘
- 01-10Springboot中@Value的使用詳解
- 01-10JavaWeb實(shí)現(xiàn)郵件發(fā)送功能
- 01-10利用Java實(shí)現(xiàn)復(fù)制Excel工作表功能
- 01-10Java實(shí)現(xiàn)動(dòng)態(tài)數(shù)字時(shí)鐘
- 01-10java基于poi導(dǎo)出excel透視表代碼實(shí)例
- 01-10java實(shí)現(xiàn)液晶數(shù)字字體顯示當(dāng)前時(shí)間
- 01-10基于Java驗(yàn)證jwt token代碼實(shí)例
- 01-10Java動(dòng)態(tài)顯示當(dāng)前日期和時(shí)間
- 01-10淺談Java中真的只有值傳遞么
隨機(jī)閱讀
- 01-11ajax實(shí)現(xiàn)頁(yè)面的局部加載
- 01-11Mac OSX 打開(kāi)原生自帶讀寫(xiě)NTFS功能(圖文
- 01-10使用C語(yǔ)言求解撲克牌的順子及n個(gè)骰子
- 01-10delphi制作wav文件的方法
- 08-05dedecms(織夢(mèng))副欄目數(shù)量限制代碼修改
- 04-02jquery與jsp,用jquery
- 01-10C#中split用法實(shí)例總結(jié)
- 01-10SublimeText編譯C開(kāi)發(fā)環(huán)境設(shè)置
- 08-05織夢(mèng)dedecms什么時(shí)候用欄目交叉功能?
- 08-05DEDE織夢(mèng)data目錄下的sessions文件夾有什