有了公平鎖,為什么還要有非公平鎖?
寫在前面
上篇文章(《??扒一扒ReentrantLock以及AQS實(shí)現(xiàn)原理??》)聊了一下Java并發(fā)包中的AQS的工作原理,也間接說(shuō)明了ReentrantLock的工作原理。
這篇文章接著來(lái)聊一個(gè)話題,Java并發(fā)包中的公平鎖與非公平鎖有啥區(qū)別?
什么是非公平鎖?
先來(lái)聊聊非公平鎖是啥,現(xiàn)在大家先回過(guò)頭來(lái)看下面這張圖。
如上圖,現(xiàn)在線程1加了鎖,然后線程2嘗試加鎖,失敗后進(jìn)入了等待隊(duì)列,處于阻塞中。然后線程1釋放了鎖,準(zhǔn)備來(lái)喚醒線程2重新嘗試加鎖。
注意一點(diǎn),此時(shí)線程2可還停留在等待隊(duì)列里啊,還沒開始嘗試重新加鎖呢!
然而,不幸的事情發(fā)生了,這時(shí)半路殺出個(gè)程咬金,來(lái)了一個(gè)線程3!線程3突然嘗試對(duì)ReentrantLock發(fā)起加鎖操作,此時(shí)會(huì)發(fā)生什么事情?
很簡(jiǎn)單!線程2還沒來(lái)得及重新嘗試加鎖呢。也就是說(shuō),還沒來(lái)得及嘗試重新執(zhí)行CAS操作將state的值從0變?yōu)?呢!線程3沖上來(lái)直接一個(gè)CAS操作,嘗試將state的值從0變?yōu)?,結(jié)果還成功了!
一旦CAS操作成功,線程3就會(huì)將“加鎖線程”這個(gè)變量設(shè)置為他自己。給大家來(lái)一張圖,看看這整個(gè)過(guò)程:
明明人家線程2規(guī)規(guī)矩矩的排隊(duì)領(lǐng)鎖呢,結(jié)果你線程3不守規(guī)矩,線程1剛釋放鎖,不分青紅皂白,直接就跑過(guò)來(lái)?yè)屜燃渔i了。
這就導(dǎo)致線程2被喚醒過(guò)后,重新嘗試加鎖執(zhí)行CAS操作,結(jié)果毫無(wú)疑問,失??!
原因很簡(jiǎn)單啊!因?yàn)榧渔iCAS操作,是要嘗試將state從0變?yōu)?,結(jié)果此時(shí)state已經(jīng)是1了,所以CAS操作一定會(huì)失敗!
一旦加鎖失敗,就會(huì)導(dǎo)致線程2繼續(xù)留在等待隊(duì)列里不斷的等著,等著線程3釋放鎖之后,再來(lái)喚醒自己,真是可憐!先來(lái)的線程2居然加不到鎖!
同樣給大家來(lái)一張圖,體會(huì)一下線程2這無(wú)助的過(guò)程:
上述的鎖策略,就是所謂的非公平鎖!
如果你用默認(rèn)的構(gòu)造函數(shù)來(lái)創(chuàng)建ReentrantLock對(duì)象,默認(rèn)的鎖策略就是非公平的。
在非公平鎖策略之下,不一定說(shuō)先來(lái)排隊(duì)的線程就就先會(huì)得到機(jī)會(huì)加鎖,而是出現(xiàn)各種線程隨意搶占的情況。
那如果要實(shí)現(xiàn)公平鎖的策略該怎么辦呢?也很簡(jiǎn)單,在構(gòu)造ReentrantLock對(duì)象的時(shí)候傳入一個(gè)true即可:
ReentrantLock lock = new ReentrantLock(true)。
此時(shí)就是說(shuō)讓他使用公平鎖的策略,那么公平鎖具體是什么意思呢?
什么是公平鎖?
咱們重新回到第一張圖,就是線程1剛剛釋放鎖之后,線程2還沒來(lái)得及重新加鎖的那個(gè)狀態(tài)。
同樣,這時(shí)假設(shè)來(lái)了一個(gè)線程3,突然殺出來(lái),想要加鎖。
如果是公平鎖的策略,那么此時(shí)線程3不會(huì)跟個(gè)愣頭青一樣盲目的直接加鎖。
他會(huì)先判斷一下:咦?AQS的等待隊(duì)列里,有沒有人在排隊(duì)?。咳绻腥嗽谂抨?duì)的話,說(shuō)明我前面有兄弟正想要加鎖啊!
如果AQS的隊(duì)列里真的有線程排著隊(duì),那我線程3就不能跟個(gè)二愣子一樣直接搶占加鎖了。
因?yàn)楝F(xiàn)在咱們是公平策略,得按照先來(lái)后到的順序依次排隊(duì),誰(shuí)先入隊(duì),誰(shuí)就先從隊(duì)列里出來(lái)加鎖!
所以,線程3此時(shí)一判斷,發(fā)現(xiàn)隊(duì)列里有人排隊(duì),自己就會(huì)乖乖的排到隊(duì)列后面去,而不會(huì)貿(mào)然加鎖!
同樣,整個(gè)過(guò)程我們用下面這張圖給大家直觀的展示一下:
上面的等待隊(duì)列中,線程3會(huì)按照公平原則直接進(jìn)入隊(duì)列尾部進(jìn)行排隊(duì)。
接著,線程2不是被喚醒了么?他就會(huì)重新嘗試進(jìn)行CAS加鎖,此時(shí)沒人跟他搶,他當(dāng)然可以加鎖成功了。
然后呢,線程2就會(huì)將state值變?yōu)?,同時(shí)設(shè)置“加鎖線程”是自己。最后,線程2自己從等待隊(duì)列里出隊(duì)。
整個(gè)過(guò)程,參見下圖:
這個(gè)就是公平鎖的策略,過(guò)來(lái)加鎖的線程全部是按照先來(lái)后到的順序,依次進(jìn)入等待隊(duì)列中排隊(duì)的,不會(huì)盲目的胡亂搶占加鎖,非常的公平。
小結(jié)
好了,通過(guò)畫圖和文字分析,相信大家都明白什么是公平鎖,什么是非公平鎖了!
不過(guò)要知道Java并發(fā)包里很多鎖默認(rèn)的策略都是非公平的,也就是可能后來(lái)的線程先加鎖,先來(lái)的線程后加鎖。
而一般情況下,非公平的策略都沒什么大問題,但是大家要對(duì)這個(gè)策略做到心里有數(shù),在開發(fā)的時(shí)候,需要自己來(lái)考慮和權(quán)衡是要用公平策略還是非公平策略。