Java中有兩種加鎖的方式:一種是用synchronized關(guān)鍵字,另一種是用Lock接口的實(shí)現(xiàn)類(lèi)桩卵。
如果你只是想要簡(jiǎn)單的加個(gè)鎖,對(duì)性能也沒(méi)什么特別的要求,用synchronized關(guān)鍵字就足夠了始花。自Java 5之后,才在java.util.concurrent.locks包下有了另外一種方式來(lái)實(shí)現(xiàn)鎖孩锡,那就是Lock酷宵。也就是說(shuō),synchronized是Java語(yǔ)言內(nèi)置的關(guān)鍵字躬窜,而Lock是一個(gè)接口浇垦,這個(gè)接口的實(shí)現(xiàn)類(lèi)在代碼層面實(shí)現(xiàn)了鎖的功能。
ReentrantLock斩披、ReadLock溜族、WriteLock 是Lock接口最重要的三個(gè)實(shí)現(xiàn)類(lèi)。對(duì)應(yīng)了“可重入鎖”垦沉、“讀鎖”和“寫(xiě)鎖”煌抒。
ReadWriteLock其實(shí)是一個(gè)工廠接口,而ReentrantReadWriteLock是ReadWriteLock的實(shí)現(xiàn)類(lèi)厕倍,它包含兩個(gè)靜態(tài)內(nèi)部類(lèi)ReadLock和WriteLock寡壮。這兩個(gè)靜態(tài)內(nèi)部類(lèi)又分別實(shí)現(xiàn)了Lock接口。
悲觀鎖 vs 樂(lè)觀鎖讹弯?CAS(樂(lè)觀鎖實(shí)現(xiàn)的基礎(chǔ))實(shí)現(xiàn)及存在的問(wèn)題况既?
悲觀鎖和樂(lè)觀鎖是一種宏觀的分類(lèi)方式,并不是特指某個(gè)鎖组民,而是在并發(fā)情況下兩種不同的策略棒仍。劃分依據(jù):線程要不要鎖住同步資源,鎖住同步資源失敗后臭胜,線程要不要阻塞(不阻塞-自旋鎖)莫其。
- 悲觀鎖(Pessimistic Lock), 就是很悲觀,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人會(huì)修改耸三。所以每次在拿數(shù)據(jù)的時(shí)候都會(huì)上鎖乱陡。這樣別人想拿這個(gè)數(shù)據(jù)就會(huì)阻塞直到它拿到鎖(共享資源每次只給一個(gè)線程使用,其它線程阻塞仪壮,用完后再把資源轉(zhuǎn)讓給其它線程)憨颠。傳統(tǒng)的關(guān)系型數(shù)據(jù)庫(kù)里邊就用到了很多這種鎖機(jī)制,比如行鎖积锅,表鎖等爽彤,讀鎖养盗,寫(xiě)鎖等,都是在做操作之前先上鎖淫茵。Java中synchronized和ReentrantLock等獨(dú)占鎖就是悲觀鎖思想的實(shí)現(xiàn)爪瓜。
- 樂(lè)觀鎖(Optimistic Lock), 就是很樂(lè)觀,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人不會(huì)修改匙瘪。所以不會(huì)上鎖铆铆,不會(huì)上鎖!但是如果想要更新數(shù)據(jù)丹喻,則會(huì)在更新前檢查在讀取至更新這段時(shí)間別人有沒(méi)有修改過(guò)這個(gè)數(shù)據(jù)薄货。如果修改過(guò),則重新讀取碍论,再次嘗試更新谅猾,循環(huán)上述步驟直到更新成功(當(dāng)然也允許更新失敗的線程放棄操作),即CAS實(shí)現(xiàn)鳍悠,在Java中java.util.concurrent.atomic包下面的原子變量類(lèi)就是使用了樂(lè)觀鎖的一種實(shí)現(xiàn)方式CAS實(shí)現(xiàn)的税娜。除此之外樂(lè)觀鎖可以使用版本號(hào)機(jī)制實(shí)現(xiàn),像數(shù)據(jù)庫(kù)提供的類(lèi)似于write_condition機(jī)制藏研,其實(shí)都是提供的樂(lè)觀鎖敬矩。
應(yīng)用場(chǎng)景:
- 悲觀鎖適合寫(xiě)操作多,先加鎖可以保證寫(xiě)操作時(shí)數(shù)據(jù)正確蠢挡。
- 樂(lè)觀鎖適合讀操作多弧岳,可以省去鎖的開(kāi)銷(xiāo),提升系統(tǒng)的吞吐量和性能业踏。
簡(jiǎn)言之禽炬,悲觀鎖阻塞事務(wù),樂(lè)觀鎖回滾重試勤家。
樂(lè)觀鎖實(shí)現(xiàn)方式:
-
CAS實(shí)現(xiàn):CAS全稱(chēng) Compare And Swap(比較與交換)腹尖,是一種無(wú)鎖算法。在不使用鎖(沒(méi)有線程被阻塞)的情況下實(shí)現(xiàn)多線程之間的變量同步伐脖。java.util.concurrent包中的原子類(lèi)就是通過(guò)CAS來(lái)實(shí)現(xiàn)了樂(lè)觀鎖热幔。
CAS算法涉及到三個(gè)操作數(shù):
- 需要讀寫(xiě)的內(nèi)存值 V。
- 進(jìn)行比較的值 A晓殊。
- 要寫(xiě)入的新值 B。
當(dāng)且僅當(dāng) V 的值等于 A 時(shí)伤提,CAS通過(guò)原子方式用新值B來(lái)更新V的值(“比較+更新”整體是一個(gè)原子操作)巫俺,否則不會(huì)執(zhí)行任何操作。一般情況下肿男,“更新”是一個(gè)不斷重試的操作介汹。java.util.concurrent.atomic 包下的類(lèi)大多是使用 CAS 操作來(lái)實(shí)現(xiàn)的却嗡。
版本號(hào)機(jī)制:一般是在數(shù)據(jù)表中加上一個(gè)數(shù)據(jù)版本號(hào)version字段,表示數(shù)據(jù)被修改的次數(shù)嘹承,當(dāng)數(shù)據(jù)被修改時(shí)窗价,version值會(huì)加一。當(dāng)線程A要更新數(shù)據(jù)值時(shí)叹卷,在讀取數(shù)據(jù)的同時(shí)也會(huì)讀取version值撼港,在提交更新時(shí),若剛才讀取到的version值為當(dāng)前數(shù)據(jù)庫(kù)中的version值相等時(shí)才更新骤竹,否則重試更新操作帝牡,直到更新成功。
CAS會(huì)產(chǎn)生哪些問(wèn)題蒙揣?
-
ABA問(wèn)題:CAS需要在操作值的時(shí)候檢查內(nèi)存值是否發(fā)生變化靶溜,沒(méi)有發(fā)生變化才會(huì)更新內(nèi)存值。但是如果內(nèi)存值原來(lái)是A懒震,后來(lái)變成了B罩息,然后又變成了A,那么CAS進(jìn)行檢查時(shí)會(huì)發(fā)現(xiàn)值沒(méi)有發(fā)生變化个扰,但是實(shí)際上是有變化的瓷炮。ABA問(wèn)題的解決思路就是在變量前面添加版本號(hào),每次變量更新的時(shí)候都把版本號(hào)加一锨匆,這樣變化過(guò)程就從“A-B-A”變成了“1A-2B-3A”崭别。
JDK從1.5開(kāi)始提供了AtomicStampedReference類(lèi)來(lái)解決ABA問(wèn)題,具體操作封裝在compareAndSet()中恐锣。compareAndSet()首先檢查當(dāng)前引用和當(dāng)前標(biāo)志與預(yù)期引用和預(yù)期標(biāo)志是否相等茅主,如果都相等,則以原子方式將引用值和標(biāo)志的值設(shè)置為給定的更新值土榴。
循環(huán)時(shí)間長(zhǎng)诀姚,開(kāi)銷(xiāo)大:對(duì)于資源競(jìng)爭(zhēng)嚴(yán)重(線程沖突嚴(yán)重)的情況,CAS操作如果長(zhǎng)時(shí)間不成功玷禽,會(huì)導(dǎo)致其一直自旋赫段,給CPU帶來(lái)非常大的開(kāi)銷(xiāo),效率低于 synchronized矢赁。
-
只能保證一個(gè)共享變量的原子操作:當(dāng)對(duì)一個(gè)共享變量執(zhí)行操作時(shí)糯笙,我們可以使用循環(huán) CAS 的方式來(lái)保證原子操作,但是對(duì)多個(gè)共享變量操作時(shí)撩银,循環(huán) CAS 就無(wú)法保證操作的原子性给涕,這個(gè)時(shí)候就可以用鎖。
Java從1.5開(kāi)始JDK提供了AtomicReference類(lèi)來(lái)保證引用對(duì)象之間的原子性,可以把多個(gè)變量放在一個(gè)對(duì)象里來(lái)進(jìn)行CAS操作够庙。
synchronized鎖升級(jí)(多個(gè)資源競(jìng)爭(zhēng)同步資源):偏向鎖 → 輕量級(jí)鎖 → 重量級(jí)鎖
劃分依據(jù):多個(gè)線程競(jìng)爭(zhēng)同步資源的流程細(xì)節(jié)不同
這四種鎖是指鎖的狀態(tài)恭应,專(zhuān)門(mén)針對(duì)synchronized的。在介紹這四種鎖狀態(tài)之前還需要介紹一些額外的知識(shí)耘眨。
首先為什么Synchronized能實(shí)現(xiàn)線程同步昼榛?在此之前,需要了解兩個(gè)重要的概念:“Java對(duì)象頭”剔难、“Monitor”胆屿。
-
Java對(duì)象頭:synchronized是悲觀鎖,在操作同步資源之前需要給同步資源先加鎖钥飞,這把鎖就是存在Java對(duì)象頭里的莺掠,而Java對(duì)象頭又是什么呢?我們以Hotspot虛擬機(jī)為例读宙,Hotspot的對(duì)象頭主要包括兩部分?jǐn)?shù)據(jù):Mark Word(標(biāo)記字段)彻秆、Klass Pointer(類(lèi)型指針)。
Mark Word:默認(rèn)存儲(chǔ)對(duì)象的HashCode结闸,分代年齡和鎖標(biāo)志位信息唇兑。這些信息都是與對(duì)象自身定義無(wú)關(guān)的數(shù)據(jù),所以Mark Word被設(shè)計(jì)成一個(gè)非固定的數(shù)據(jù)結(jié)構(gòu)以便在極小的空間內(nèi)存存儲(chǔ)盡量多的數(shù)據(jù)桦锄。它會(huì)根據(jù)對(duì)象的狀態(tài)復(fù)用自己的存儲(chǔ)空間扎附,也就是說(shuō)在運(yùn)行期間Mark Word里存儲(chǔ)的數(shù)據(jù)會(huì)隨著鎖標(biāo)志位的變化而變化。
Klass Point:對(duì)象指向它的類(lèi)元數(shù)據(jù)的指針结耀,虛擬機(jī)通過(guò)這個(gè)指針來(lái)確定這個(gè)對(duì)象是哪個(gè)類(lèi)的實(shí)例留夜。
-
Monitor:Monitor可以理解為一個(gè)同步工具或一種同步機(jī)制,通常被描述為一個(gè)對(duì)象图甜。每一個(gè)Java對(duì)象就有一把看不見(jiàn)的鎖碍粥,稱(chēng)為內(nèi)部鎖或者M(jìn)onitor鎖。
Monitor是線程私有的數(shù)據(jù)結(jié)構(gòu)黑毅,每一個(gè)線程都有一個(gè)可用monitor record列表嚼摩,同時(shí)還有一個(gè)全局的可用列表。每一個(gè)被鎖住的對(duì)象都會(huì)和一個(gè)monitor關(guān)聯(lián)矿瘦,同時(shí)monitor中有一個(gè)Owner字段存放擁有該鎖的線程的唯一標(biāo)識(shí)枕面,表示該鎖被這個(gè)線程占用。
現(xiàn)在話題回到synchronized缚去,synchronized通過(guò)Monitor來(lái)實(shí)現(xiàn)線程同步潮秘,Monitor是依賴于底層的操作系統(tǒng)的Mutex Lock(互斥鎖)來(lái)實(shí)現(xiàn)的線程同步。
為什么會(huì)出現(xiàn)synchronized三種鎖易结?
“阻塞或喚醒一個(gè)Java線程需要操作系統(tǒng)切換CPU狀態(tài)來(lái)完成枕荞,這種狀態(tài)轉(zhuǎn)換需要耗費(fèi)處理器時(shí)間稠通。如果同步代碼塊中的內(nèi)容過(guò)于簡(jiǎn)單,狀態(tài)轉(zhuǎn)換消耗的時(shí)間有可能比用戶代碼執(zhí)行的時(shí)間還要長(zhǎng)”买猖。這種方式就是synchronized最初實(shí)現(xiàn)同步的方式,這就是JDK 6之前synchronized效率低的原因滋尉。這種依賴于操作系統(tǒng)Mutex Lock所實(shí)現(xiàn)的鎖我們稱(chēng)之為“重量級(jí)鎖”玉控,JDK 6中為了減少獲得鎖和釋放鎖帶來(lái)的性能消耗,引入了“偏向鎖”和“輕量級(jí)鎖”狮惜。
-
偏向鎖:偏向鎖是指一段同步代碼一直被一個(gè)線程所訪問(wèn)高诺,那么該線程會(huì)自動(dòng)獲取鎖,降低獲取鎖的代價(jià)碾篡。
- 多數(shù)情況下虱而,鎖總是由同一線程多次獲得,不存在多線程競(jìng)爭(zhēng)开泽,所以出現(xiàn)了偏向鎖牡拇。其目標(biāo)就是在只有一個(gè)線程執(zhí)行同步代碼塊時(shí)能夠提高性能。
- 當(dāng)一個(gè)線程訪問(wèn)同步代碼塊并獲取鎖時(shí)穆律,會(huì)在Mark Word里存儲(chǔ)鎖偏向的線程ID惠呼。在線程進(jìn)入和退出同步塊時(shí)不再通過(guò)CAS操作來(lái)加鎖和解鎖,而是檢測(cè)Mark Word里是否存儲(chǔ)著指向當(dāng)前線程的偏向鎖峦耘。引入偏向鎖是為了在無(wú)多線程競(jìng)爭(zhēng)的情況下盡量減少不必要的輕量級(jí)鎖執(zhí)行路徑剔蹋,因?yàn)檩p量級(jí)鎖的獲取及釋放依賴多次CAS原子指令,而偏向鎖只需要在置換ThreadID的時(shí)候依賴一次CAS原子指令即可辅髓。
- 偏向鎖在JDK 6及以后的JVM里是默認(rèn)啟用的泣崩。可以通過(guò)JVM參數(shù)關(guān)閉偏向鎖:-XX:-UseBiasedLocking=false洛口,關(guān)閉之后程序默認(rèn)會(huì)進(jìn)入輕量級(jí)鎖狀態(tài)矫付。
-
輕量級(jí)鎖:當(dāng)偏向鎖被另外的線程訪問(wèn),偏向鎖就會(huì)升級(jí)為輕量級(jí)鎖绍弟,其他線程會(huì)通過(guò)自旋的形式嘗試獲取鎖技即,不會(huì)阻塞,從而提高性能樟遣。若當(dāng)前只有一個(gè)等待線程而叼,則該線程通過(guò)自旋進(jìn)行等待(cpu在做無(wú)用功,一般設(shè)定最大等待時(shí)間豹悬,達(dá)到最大等待時(shí)間葵陵,停止自旋進(jìn)入阻塞狀態(tài))。
- 在代碼進(jìn)入同步塊的時(shí)候瞻佛,如果同步對(duì)象鎖狀態(tài)為無(wú)鎖狀態(tài)脱篙,虛擬機(jī)首先將在當(dāng)前線程的棧幀中建立一個(gè)名為鎖記錄(Lock Record)的空間娇钱,用于存儲(chǔ)鎖對(duì)象目前的Mark Word的拷貝,然后拷貝對(duì)象頭中的Mark Word復(fù)制到鎖記錄中绊困。
- 拷貝成功后文搂,虛擬機(jī)將使用CAS操作嘗試將對(duì)象的Mark Word更新為指向Lock Record的指針,并將Lock Record里的owner指針指向?qū)ο蟮腗ark Word秤朗。
- 如果這個(gè)更新動(dòng)作成功了煤蹭,那么這個(gè)線程就擁有了該對(duì)象的鎖,并且對(duì)象Mark Word的鎖標(biāo)志位設(shè)置為“00”取视,表示此對(duì)象處于輕量級(jí)鎖定狀態(tài)硝皂。
- 如果輕量級(jí)鎖的更新操作失敗了,虛擬機(jī)首先會(huì)檢查對(duì)象的Mark Word是否指向當(dāng)前線程的棧幀作谭,如果是就說(shuō)明當(dāng)前線程已經(jīng)擁有了這個(gè)對(duì)象的鎖稽物,那就可以直接進(jìn)入同步塊繼續(xù)執(zhí)行,否則說(shuō)明多個(gè)線程競(jìng)爭(zhēng)鎖折欠。
-
重量級(jí)鎖:當(dāng)自旋超過(guò)一定的次數(shù)贝或,或者一個(gè)線程在持有鎖,一個(gè)在自旋锐秦,又有第三個(gè)來(lái)訪時(shí)傀缩,輕量級(jí)鎖升級(jí)為重量級(jí)鎖。
- 升級(jí)為重量級(jí)鎖時(shí)农猬,鎖標(biāo)志的狀態(tài)值變?yōu)椤?strong>10”赡艰,此時(shí)Mark Word中存儲(chǔ)的是指向重量級(jí)鎖的指針,此時(shí)等待鎖的線程都會(huì)進(jìn)入阻塞狀態(tài)斤葱。
三種鎖的區(qū)別和總結(jié):
- 偏向鎖:通過(guò)對(duì)比Mark Word解決加鎖問(wèn)題慷垮,避免執(zhí)行CAS操作帶來(lái)的額外消耗。但是存在鎖競(jìng)爭(zhēng)會(huì)帶來(lái)額外的鎖撤銷(xiāo)的消耗揍堕,只適用一個(gè)線程訪問(wèn)同步代碼的場(chǎng)景料身。
- 輕量級(jí)鎖:是通過(guò)用CAS操作和自旋來(lái)解決加鎖問(wèn)題,避免線程阻塞和喚醒而影響性能衩茸,程序響應(yīng)快芹血。但是如果線程始終得不到鎖會(huì)自旋消耗cpu,適用于追求響應(yīng)時(shí)間楞慈,同步代碼塊執(zhí)行快的場(chǎng)景幔烛。
- 重量級(jí)鎖:是將除了擁有鎖的線程以外的線程都阻塞,線程競(jìng)爭(zhēng)不使用自旋消耗cpu囊蓝。但是線程會(huì)出現(xiàn)阻塞饿悬,響應(yīng)時(shí)間慢,適用于追求吞吐量聚霜、同步代碼塊執(zhí)行慢的場(chǎng)景狡恬。
ps:同步代碼塊(資源):即有synchronized修飾符修飾的語(yǔ)句塊珠叔,被該關(guān)鍵詞修飾的語(yǔ)句塊,將加上內(nèi)置鎖弟劲,實(shí)現(xiàn)同步祷安。
公平鎖 vs 非公平鎖?
劃分依據(jù):多個(gè)線程競(jìng)爭(zhēng)鎖時(shí)要不要排隊(duì)
-
公平鎖:公平鎖是指多個(gè)線程按照申請(qǐng)鎖的順序來(lái)獲取鎖兔乞,線程直接進(jìn)入隊(duì)列中排隊(duì)辆憔,隊(duì)列中的第一個(gè)線程才能獲得鎖。
公平鎖的優(yōu)點(diǎn)是等待鎖的線程不會(huì)餓死报嵌。缺點(diǎn)是整體吞吐效率相對(duì)非公平鎖要低,等待隊(duì)列中除第一個(gè)線程以外的所有線程都會(huì)阻塞熊榛,CPU喚醒阻塞線程的開(kāi)銷(xiāo)比非公平鎖大锚国。
-
非公平鎖:非公平鎖是多個(gè)線程加鎖時(shí)直接嘗試獲取鎖,獲取不到才會(huì)到等待隊(duì)列的隊(duì)尾等待玄坦。
但如果此時(shí)鎖剛好可用血筑,那么這個(gè)線程可以無(wú)需阻塞直接獲取到鎖,所以非公平鎖有可能出現(xiàn)后申請(qǐng)鎖的線程先獲取鎖的場(chǎng)景煎楣。非公平鎖的優(yōu)點(diǎn)是可以減少喚起線程的開(kāi)銷(xiāo)豺总,整體的吞吐效率高,因?yàn)榫€程有幾率不阻塞直接獲得鎖择懂,CPU不必喚醒所有線程喻喳。缺點(diǎn)是處于等待隊(duì)列中的線程可能會(huì)餓死,或者等很久才會(huì)獲得鎖困曙。
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
//創(chuàng)建一個(gè)非公平鎖表伦,默認(rèn)是非公平鎖
Lock lock = new ReentrantLock();
Lock lock = new ReentrantLock(false);
//創(chuàng)建一個(gè)公平鎖,構(gòu)造傳參true
Lock lock = new ReentrantLock(true);
根據(jù)參數(shù)決定其內(nèi)部是公平鎖還是非公平鎖慷丽。公平鎖與非公平鎖的lock()方法唯一的區(qū)別就在于公平鎖在獲取同步狀態(tài)時(shí)多了一個(gè)限制條件:hasQueuedPredecessors()蹦哼。源碼自行查看,該限制條件作用:主要是判斷當(dāng)前線程是否位于同步隊(duì)列中的第一個(gè)要糊。如果是則返回true纲熏,否則返回false。
- 對(duì)ReentrantLock類(lèi)而言锄俄,通過(guò)構(gòu)造函數(shù)傳參可以指定該鎖是否是公平鎖局劲,默認(rèn)是非公平鎖。一般情況下奶赠,非公平鎖的吞吐量比公平鎖大容握,如果沒(méi)有特殊要求,優(yōu)先使用非公平鎖车柠。
- 對(duì)于synchronized而言剔氏,它也是一種非公平鎖塑猖,但是并沒(méi)有任何辦法使其變成公平鎖。
綜上谈跛,公平鎖就是通過(guò)同步隊(duì)列來(lái)實(shí)現(xiàn)多個(gè)線程按照申請(qǐng)鎖的順序來(lái)獲取鎖羊苟,從而實(shí)現(xiàn)公平的特性。非公平鎖加鎖時(shí)不考慮排隊(duì)等待問(wèn)題感憾,直接嘗試獲取鎖蜡励,所以存在后申請(qǐng)卻先獲得鎖的情況。
可重入鎖 vs 非可重入鎖阻桅? ReentrantLock 的可重入是怎么實(shí)現(xiàn)的凉倚?
劃分依據(jù):一個(gè)線程中的多個(gè)流程能不能獲取同一把鎖
可重入鎖又名遞歸鎖,是指在同一個(gè)線程在外層方法獲取鎖的時(shí)候嫂沉,再進(jìn)入該線程的內(nèi)層方法會(huì)自動(dòng)獲取鎖(前提鎖對(duì)象得是同一個(gè)對(duì)象或者class)稽寒,不會(huì)因?yàn)橹耙呀?jīng)獲取過(guò)還沒(méi)釋放而阻塞。
Java中ReentrantLock和synchronized都是可重入鎖趟章,可重入鎖的一個(gè)優(yōu)點(diǎn)是可一定程度避免死鎖杏糙。
為什么非可重入鎖在重復(fù)調(diào)用同步資源時(shí)會(huì)出現(xiàn)死鎖?
通過(guò)重入鎖ReentrantLock以及非可重入鎖NonReentrantLock為例:首先ReentrantLock和NonReentrantLock都繼承父類(lèi)AQS蚓土,其父類(lèi)AQS中維護(hù)了一個(gè)同步狀態(tài)status來(lái)計(jì)數(shù)重入次數(shù)宏侍,status初始值為0。
- 當(dāng)線程嘗試獲取鎖時(shí)蜀漆,可重入鎖先嘗試獲取并更新status值谅河,如果status == 0表示沒(méi)有其他線程在執(zhí)行同步代碼,則把status置為1确丢,當(dāng)前線程開(kāi)始執(zhí)行旧蛾。如果status != 0,則判斷當(dāng)前線程是否是獲取到這個(gè)鎖的線程蠕嫁,如果是的話執(zhí)行status+1锨天,且當(dāng)前線程可以再次獲取鎖。而非可重入鎖是直接去獲取并嘗試更新當(dāng)前status的值剃毒,如果status != 0的話會(huì)導(dǎo)致其獲取鎖失敗病袄,當(dāng)前線程阻塞。
- 釋放鎖時(shí)赘阀,可重入鎖同樣先獲取當(dāng)前status的值益缠,在當(dāng)前線程是持有鎖的線程的前提下。如果status-1 == 0基公,則表示當(dāng)前線程所有重復(fù)獲取鎖的操作都已經(jīng)執(zhí)行完畢幅慌,然后該線程才會(huì)真正釋放鎖。而非可重入鎖則是在確定當(dāng)前線程是持有鎖的線程之后轰豆,直接將status置為0胰伍,將鎖釋放齿诞。
讀寫(xiě)鎖(共享鎖、獨(dú)享鎖)?
劃分依據(jù):多個(gè)線程能不能共享一把鎖骂租,獨(dú)享鎖與共享鎖也是通過(guò)AQS來(lái)實(shí)現(xiàn)的祷杈,通過(guò)實(shí)現(xiàn)不同的方法,來(lái)實(shí)現(xiàn)獨(dú)享或者共享渗饮。
讀鎖(共享鎖):該鎖可被多個(gè)線程所持有但汞。如果線程T對(duì)數(shù)據(jù)A加上共享鎖后,則其他線程只能對(duì)A再加共享鎖互站,不能加排它鎖私蕾。獲得共享鎖的線程只能讀數(shù)據(jù),不能修改數(shù)據(jù)胡桃。
寫(xiě)鎖(互斥鎖/獨(dú)享鎖/排他鎖):該鎖一次只能被一個(gè)線程所持有踩叭。如果線程T對(duì)數(shù)據(jù)A加上排它鎖后,則其他線程不能再對(duì)A加任何類(lèi)型的鎖标捺。獲得排它鎖的線程即能讀數(shù)據(jù)又能修改數(shù)據(jù)。JDK中的synchronized和JUC中Lock的實(shí)現(xiàn)類(lèi)就是互斥鎖揉抵。
讀寫(xiě)鎖是悲觀鎖策略亡容!因?yàn)?strong>讀寫(xiě)鎖并沒(méi)有在更新前判斷值有沒(méi)有被修改過(guò),而是在加鎖前決定應(yīng)該用讀鎖還是寫(xiě)鎖冤今。
Lock 和 synchronized 有什么區(qū)別闺兢?
- 性質(zhì)不同:synchronized是Java內(nèi)置關(guān)鍵字,在JVM層面戏罢,Lock是個(gè)Java類(lèi)屋谭;
- 加鎖對(duì)象不同:synchronized 可以給類(lèi)、方法龟糕、代碼塊加鎖桐磁;而 lock 只能給代碼塊加鎖。
- 是否會(huì)產(chǎn)生死鎖:synchronized 不需要手動(dòng)獲取鎖和釋放鎖讲岁,使用簡(jiǎn)單我擂,發(fā)生異常會(huì)自動(dòng)釋放鎖,不會(huì)造成死鎖缓艳;而 lock 需要自己加鎖和釋放鎖校摩,如果使用不當(dāng)沒(méi)有 unLock()去釋放鎖就會(huì)造成死鎖。
- 是否知道成功獲取鎖:通過(guò) Lock 可以知道有沒(méi)有成功獲取鎖阶淘,而 synchronized 卻無(wú)法辦到衙吩。
- 是否是中斷鎖:Lock是可以中斷鎖,Synchronized是非中斷鎖溪窒,必須等待線程執(zhí)行完成釋放鎖坤塞。
ps:可中斷鎖:可以響應(yīng)中斷的鎖冯勉,Java并沒(méi)有提供任何直接中斷某線程的方法,只提供了中斷機(jī)制尺锚。
Java的中斷不能直接終止線程珠闰,而是需要被中斷的線程自己決定怎么處理。如果線程A持有鎖瘫辩,線程B等待獲取該鎖伏嗜。由于線程A持有鎖的時(shí)間過(guò)長(zhǎng),線程B不想繼續(xù)等待了伐厌,我們可以讓線程B中斷自己或者在別的線程里中斷它承绸,這種就是可中斷鎖。
synchronized 和 ReentrantLock 區(qū)別是什么挣轨?如何選擇呢军熏?
可重入鎖 ReentrantLock 是 Lock 最常見(jiàn)的實(shí)現(xiàn),與 synchronized 一樣可重入卷扮,不過(guò)它增加了一些高級(jí)功能:
- 等待可中斷:持有鎖的線程長(zhǎng)期不釋放鎖時(shí)荡澎,正在等待的線程可以選擇放棄等待而處理其他事情。
- 自定義公平鎖:synchronized 是非公平的晤锹,ReentrantLock 在默認(rèn)情況下是非公平的摩幔,可以通過(guò)構(gòu)造方法指定公平鎖。一旦使用了公平鎖鞭铆,性能會(huì)急劇下降或衡,影響吞吐量。
- 鎖綁定多個(gè)條件:一個(gè) ReentrantLock 可以同時(shí)綁定多個(gè) Condition车遂。synchronized 中鎖對(duì)象的 wait 跟 notify 可以實(shí)現(xiàn)一個(gè)隱含條件封断,如果要和多個(gè)條件關(guān)聯(lián)就不得不額外添加鎖,而 ReentrantLock 可以多次調(diào)用 newCondition 創(chuàng)建多個(gè)條件舶担。
一般優(yōu)先考慮使用 synchronized:
- synchronized 是語(yǔ)法層面的同步坡疼,足夠簡(jiǎn)單。
- Lock 必須確保在 finally 中釋放鎖衣陶,否則一旦拋出異常有可能永遠(yuǎn)不會(huì)釋放鎖回梧。使用 synchronized 可以由 JVM 來(lái)確保即使出現(xiàn)異常鎖也能正常釋放。
- JVM 更容易針對(duì)synchronized 優(yōu)化祖搓,因?yàn)?JVM 可以在線程和對(duì)象的元數(shù)據(jù)中記錄 synchronized 中鎖的相關(guān)信息狱意,而使用 Lock 的話 JVM 很難得知具體哪些鎖對(duì)象是由特定線程持有的。
巨人的肩膀:
https://zhuanlan.zhihu.com/p/71156910#ref_1
https://zhuanlan.zhihu.com/p/50098743