正文
開始聊之前竹伸,我先大概說一下他們兩者的定義,幫大家回顧或者認(rèn)識一下礼烈。
公平鎖:多個(gè)線程按照申請鎖的順序去獲得鎖弧满,線程會(huì)直接進(jìn)入隊(duì)列去排隊(duì),永遠(yuǎn)都是隊(duì)列的第一位才能得到鎖此熬。
- 優(yōu)點(diǎn):所有的線程都能得到資源庭呜,不會(huì)餓死在隊(duì)列中。
- 缺點(diǎn):吞吐量會(huì)下降很多犀忱,隊(duì)列里面除了第一個(gè)線程募谎,其他的線程都會(huì)阻塞,cpu喚醒阻塞線程的開銷會(huì)很大阴汇。
非公平鎖:多個(gè)線程去獲取鎖的時(shí)候数冬,會(huì)直接去嘗試獲取,獲取不到搀庶,再去進(jìn)入等待隊(duì)列拐纱,如果能獲取到铜异,就直接獲取到鎖。
- 優(yōu)點(diǎn):可以減少CPU喚醒線程的開銷秸架,整體的吞吐效率會(huì)高點(diǎn)揍庄,CPU也不必取喚醒所有線程,會(huì)減少喚起線程的數(shù)量咕宿。
- 缺點(diǎn):你們可能也發(fā)現(xiàn)了币绩,這樣可能導(dǎo)致隊(duì)列中間的線程一直獲取不到鎖或者長時(shí)間獲取不到鎖,導(dǎo)致餓死府阀。
我舉個(gè)例子給他家通俗易懂的講一下的缆镣,想了好幾天終于在前天跟三歪去肯德基買早餐排隊(duì)的時(shí)候發(fā)現(xiàn)了怎么舉例了。
現(xiàn)在是早餐時(shí)間试浙,敖丙想去kfc搞個(gè)早餐董瞻,發(fā)現(xiàn)有很多人了,一過去沒多想田巴,就乖乖到隊(duì)尾排隊(duì)钠糊,這樣大家都覺得很公平,先到先得壹哺,所以這是公平鎖咯抄伍。
那非公平鎖就是,敖丙過去買早餐管宵,發(fā)現(xiàn)大家都在排隊(duì)截珍,但是敖丙這個(gè)人有點(diǎn)渣的,就是喜歡插隊(duì)箩朴,那他就直接懟到第一位那去岗喉,后面的雞蛋,米豆都不行炸庞,我插隊(duì)也不敢說什么钱床,只能默默忍受了。
但是偶爾埠居,雞蛋也會(huì)崛起查牌,叫我滾到后面排隊(duì),我也是欺軟怕硬滥壕,默默到后面排隊(duì)僧免,就插隊(duì)失敗了。
介紹完簡單的例子捏浊,大家可能會(huì)說懂衩,渣丙,這個(gè)我也知道的啊。
我們是不是應(yīng)該回歸真正的實(shí)現(xiàn)了浊洞,其實(shí)在大家經(jīng)常使用的ReentrantLock中就有相關(guān)公平鎖牵敷,非公平鎖的實(shí)現(xiàn)了。
大家還記得我在樂觀鎖法希、悲觀鎖章節(jié)提到的Sync類么枷餐,是ReentrantLock他本身的一個(gè)內(nèi)部類,他繼承了AbstractQueuedSynchronizer苫亦,我們在操作鎖的大部分操作毛肋,都是Sync本身去實(shí)現(xiàn)的。
Sync呢又分別有兩個(gè)子類:FairSync和NofairSync
他們子類的名字就可以見名知意了屋剑,公平和不公平那又是怎么在代碼層面體現(xiàn)的呢润匙?
公平鎖:
你可以看到,他加了一個(gè)hasQueuedPredecessors的判斷唉匾,那他判斷里面有些什么玩意呢孕讳?
代碼的大概意思也是判斷當(dāng)前的線程是不是位于同步隊(duì)列的首位,是就是返回true巍膘,否就返回false厂财。
我總覺得寫到這里就應(yīng)該差不多了,但是我坐下來峡懈,靜靜的思考之后發(fā)現(xiàn)璃饱,還是差了點(diǎn)什么。
上次聊過ReentrantLock了肪康,但是AQS什么的我都只是提了一嘴荚恶,一個(gè)線程進(jìn)來,他整個(gè)處理鏈路到底是怎樣的呢梅鹦?
公平鎖到底公平不公平呢?讓我們一起跟著丙丙走進(jìn)ReentrantLock的內(nèi)心世界冗锁。
上面提了這么多齐唆,我想你應(yīng)該是有所了解了,那一個(gè)線程進(jìn)來ReentrantLock這個(gè)渣男是怎么不公平的呢冻河?(默認(rèn)是非公平鎖)
我先畫個(gè)圖箍邮,幫助大家了解下細(xì)節(jié):
ReentrantLock的Sync繼承了AbstractQueuedSynchronizer也就是我們常說的AQS
他也是ReentrantLock加鎖釋放鎖的核心,大致的內(nèi)容我之前一期提到了叨叙,我就不過多贅述了锭弊,他們看看一次加鎖的過程吧。
A線程準(zhǔn)備進(jìn)去獲取鎖擂错,首先判斷了一下state狀態(tài)味滞,發(fā)現(xiàn)是0,所以可以CAS成功,并且修改了當(dāng)前持有鎖的線程為自己剑鞍。
這個(gè)時(shí)候B線程也過來了昨凡,也是一上來先去判斷了一下state狀態(tài),發(fā)現(xiàn)是1蚁署,那就CAS失敗了便脊,真晦氣,只能乖乖去等待隊(duì)列光戈,等著喚醒了哪痰,先去睡一覺吧。
A持有久了久妆,也有點(diǎn)膩了晌杰,準(zhǔn)備釋放掉鎖,給別的仔一個(gè)機(jī)會(huì)镇饺,所以改了state狀態(tài)乎莉,抹掉了持有鎖線程的痕跡,準(zhǔn)備去叫醒B奸笤。
這個(gè)時(shí)候有個(gè)帶綠帽子的仔C過來了惋啃,發(fā)現(xiàn)state怎么是0啊,果斷CAS修改為1监右,還修改了當(dāng)前持有鎖的線程為自己边灭。
B線程被A叫醒準(zhǔn)備去獲取鎖,發(fā)現(xiàn)state居然是1健盒,CAS就失敗了绒瘦,只能失落的繼續(xù)回去等待隊(duì)列,路線還不忘罵A渣男扣癣,怎么騙自己惰帽,欺騙我的感情。
諾以上就是一個(gè)非公平鎖的線程父虑,這樣的情況就有可能像B這樣的線程長時(shí)間無法得到資源该酗,優(yōu)點(diǎn)就是可能有的線程減少了等待時(shí)間,提高了利用率士嚎。
現(xiàn)在都是默認(rèn)非公平了呜魄,想要公平就得給構(gòu)造器傳值true。
ReentrantLock lock = new ReentrantLock(true);
說完非公平莱衩,那我也說一下公平的過程吧:
線A現(xiàn)在想要獲得鎖爵嗅,先去判斷下state,發(fā)現(xiàn)也是0笨蚁,去看了看隊(duì)列睹晒,自己居然是第一位趟庄,果斷修改了持有線程為自己。
線程b過來了册招,去判斷一下state岔激,嗯哼?居然是state=1是掰,那cas就失敗了呀虑鼎,所以只能乖乖去排隊(duì)了。
線程A暖男來了键痛,持有沒多久就釋放了炫彩,改掉了所有的狀態(tài)就去喚醒線程B了,這個(gè)時(shí)候線程C進(jìn)來了絮短,但是他先判斷了下state發(fā)現(xiàn)是0江兢,以為有戲,然后去看了看隊(duì)列丁频,發(fā)現(xiàn)前面有人了杉允,作為新時(shí)代的良好市民,果斷排隊(duì)去了席里。
線程B得到A的召喚叔磷,去判斷state了,發(fā)現(xiàn)值為0奖磁,自己也是隊(duì)列的第一位改基,那很香呀,可以得到了咖为。
總結(jié):
總結(jié)我不說話了秕狰,但是去獲取鎖判斷的源碼,箭頭所指的位置躁染,現(xiàn)在是不是都被我合理的解釋了鸣哀,當(dāng)前線程,state吞彤,是否是0我衬,是否是當(dāng)前線程等等,都去思考下备畦。