年底了我裁完兄弟自己也離職了,復習了Java鎖的底層準備面試...

一、寫在前面
上篇文章:《??SpringBoot3.0都正式發(fā)布了,嘗鮮之前先搞明白AQS底層再說??》聊了一下java并發(fā)包中的AQS的工作原理,也間接說明了ReentrantLock的工作原理。
這篇文章接著來聊一個話題,java并發(fā)包中的公平鎖與非公平鎖有啥區(qū)別?
二、什么是非公平鎖?
先來聊聊非公平鎖是啥,現(xiàn)在大家先回過頭來看下面這張圖。

如上圖,現(xiàn)在線程1加了鎖,然后線程2嘗試加鎖,失敗后進入了等待隊列,處于阻塞中。然后線程1釋放了鎖,準備來喚醒線程2重新嘗試加鎖。
注意一點,此時線程2可還停留在等待隊列里啊,還沒開始嘗試重新加鎖呢!
然而,不幸的事情發(fā)生了,這時半路殺出個程咬金,來了一個線程3!線程3突然嘗試對ReentrantLock發(fā)起加鎖操作,此時會發(fā)生什么事情?
很簡單!線程2還沒來得及重新嘗試加鎖呢。也就是說,還沒來得及嘗試重新執(zhí)行CAS操作將state的值從0變?yōu)?呢!線程3沖上來直接一個CAS操作,嘗試將state的值從0變?yōu)?,結果還成功了!
一旦CAS操作成功,線程3就會將“加鎖線程”這個變量設置為他自己。給大家來一張圖,看看這整個過程:

明明人家線程2規(guī)規(guī)矩矩的排隊領鎖呢,結果你線程3不守規(guī)矩,線程1剛釋放鎖,不分青紅皂白,直接就跑過來搶先加鎖了。
這就導致線程2被喚醒過后,重新嘗試加鎖執(zhí)行CAS操作,結果毫無疑問,失??!
原因很簡單?。∫驗榧渔iCAS操作,是要嘗試將state從0變?yōu)?,結果此時state已經(jīng)是1了,所以CAS操作一定會失??!
一旦加鎖失敗,就會導致線程2繼續(xù)留在等待隊列里不斷的等著,等著線程3釋放鎖之后,再來喚醒自己,真是可憐!先來的線程2居然加不到鎖!
同樣給大家來一張圖,體會一下線程2這無助的過程:

上述的鎖策略,就是所謂的非公平鎖!
如果你用默認的構造函數(shù)來創(chuàng)建ReentrantLock對象,默認的鎖策略就是非公平的。
在非公平鎖策略之下,不一定說先來排隊的線程就就先會得到機會加鎖,而是出現(xiàn)各種線程隨意搶占的情況。
那如果要實現(xiàn)公平鎖的策略該怎么辦呢?也很簡單,在構造ReentrantLock對象的時候傳入一個true即可:
ReentrantLock lock = new ReentrantLock(true)。
此時就是說讓他使用公平鎖的策略,那么公平鎖具體是什么意思呢?
三、什么是公平鎖?
咱們重新回到第一張圖,就是線程1剛剛釋放鎖之后,線程2還沒來得及重新加鎖的那個狀態(tài)。

同樣,這時假設來了一個線程3,突然殺出來,想要加鎖。
如果是公平鎖的策略,那么此時線程3不會跟個愣頭青一樣盲目的直接加鎖。
他會先判斷一下:咦?AQS的等待隊列里,有沒有人在排隊?。咳绻腥嗽谂抨牭脑?,說明我前面有兄弟正想要加鎖??!
如果AQS的隊列里真的有線程排著隊,那我線程3就不能跟個二愣子一樣直接搶占加鎖了。
因為現(xiàn)在咱們是公平策略,得按照先來后到的順序依次排隊,誰先入隊,誰就先從隊列里出來加鎖!
所以,線程3此時一判斷,發(fā)現(xiàn)隊列里有人排隊,自己就會乖乖的排到隊列后面去,而不會貿(mào)然加鎖!
同樣,整個過程我們用下面這張圖給大家直觀的展示一下:

上面的等待隊列中,線程3會按照公平原則直接進入隊列尾部進行排隊。
接著,線程2不是被喚醒了么?他就會重新嘗試進行CAS加鎖,此時沒人跟他搶,他當然可以加鎖成功了。
然后呢,線程2就會將state值變?yōu)?,同時設置“加鎖線程”是自己。最后,線程2自己從等待隊列里出隊。
整個過程,參見下圖:

這個就是公平鎖的策略,過來加鎖的線程全部是按照先來后到的順序,依次進入等待隊列中排隊的,不會盲目的胡亂搶占加鎖,非常的公平。
四、小結
好了,通過畫圖和文字分析,相信大家都明白什么是公平鎖,什么是非公平鎖了!
不過要知道java并發(fā)包里很多鎖默認的策略都是非公平的,也就是可能后來的線程先加鎖,先來的線程后加鎖。
而一般情況下,非公平的策略都沒什么大問題,但是大家要對這個策略做到心里有數(shù),在開發(fā)的時候,需要自己來考慮和權衡是要用公平策略還是非公平策略。


























