聊聊Java併發面試問題之公平鎖與非公平鎖是啥?

Java工程師攻略發表於2019-04-16

一.什麼是非公平鎖?

先來聊聊非公平鎖是啥,現在大家先回過頭來看下面這張圖。

image.png
如上圖,現線上程1加了鎖,然後執行緒2嘗試加鎖,失敗後進入了等待佇列,處於阻塞中。然後執行緒1釋放了鎖,準備來喚醒執行緒2重新嘗試加鎖。

注意一點,此時執行緒2可還停留在等待佇列裡啊,還沒開始嘗試重新加鎖呢!

然而,不幸的事情發生了,這時半路殺出個程咬金,來了一個執行緒3!執行緒3突然嘗試對ReentrantLock發起加鎖操作,此時會發生什麼事情?

很簡單!執行緒2還沒來得及重新嘗試加鎖呢。也就是說,還沒來得及嘗試重新執行CAS操作將state的值從0變為1呢!執行緒3衝上來直接一個CAS操作,嘗試將state的值從0變為1,結果還成功了!

一旦CAS操作成功,執行緒3就會將“加鎖執行緒”這個變數設定為他自己。給大家來一張圖,看看這整個過程:

image.png

明明人家執行緒2規規矩矩的排隊領鎖呢,結果你執行緒3不守規矩,執行緒1剛釋放鎖,不分青紅皁白,直接就跑過來搶先加鎖了。

這就導致執行緒2被喚醒過後,重新嘗試加鎖執行CAS操作,結果毫無疑問,失敗!

原因很簡單啊!因為加鎖CAS操作,是要嘗試將state從0變為1,結果此時state已經是1了,所以CAS操作一定會失敗!

一旦加鎖失敗,就會導致執行緒2繼續留在等待佇列裡不斷的等著,等著執行緒3釋放鎖之後,再來喚醒自己,真是可憐!先來的執行緒2居然加不到鎖!

同樣給大家來一張圖,體會一下執行緒2這無助的過程:

image.png
上述的鎖策略,就是所謂的非公平鎖! 如果你用預設的建構函式來建立ReentrantLock物件,預設的鎖策略就是非公平的。

在非公平鎖策略之下,不一定說先來排隊的執行緒就就先會得到機會加鎖,而是出現各種執行緒隨意搶佔的情況。

**那如果要實現公平鎖的策略該怎麼辦呢?**也很簡單,在構造ReentrantLock物件的時候傳入一個true即可: ReentrantLock lock = new ReentrantLock(true) 此時就是說讓他使用公平鎖的策略,那麼公平鎖具體是什麼意思呢?

二.什麼是公平鎖?

我們們重新回到第一張圖,就是執行緒1剛剛釋放鎖之後,執行緒2還沒來得及重新加鎖的那個狀態。

image.png
同樣,這時假設來了一個執行緒3,突然殺出來,想要加鎖。

如果是公平鎖的策略,那麼此時執行緒3不會跟個愣頭青一樣盲目的直接加鎖。

他會先判斷一下:咦?AQS的等待佇列裡,有沒有人在排隊啊?如果有人在排隊的話,說明我前面有兄弟正想要加鎖啊!

如果AQS的佇列裡真的有執行緒排著隊,那我執行緒3就不能跟個二愣子一樣直接搶佔加鎖了。

因為現在我們們是公平策略,得按照先來後到的順序依次排隊,誰先入隊,誰就先從佇列裡出來加鎖!

所以,執行緒3此時一判斷,發現佇列裡有人排隊,自己就會乖乖的排到佇列後面去,而不會貿然加鎖!

同樣,整個過程我們用下面這張圖給大家直觀的展示一下:

image.png
上面的等待佇列中,執行緒3會按照公平原則直接進入佇列尾部進行排隊。

接著,執行緒2不是被喚醒了麼?他就會重新嘗試進行CAS加鎖,此時沒人跟他搶,他當然可以加鎖成功了。

然後呢,執行緒2就會將state值變為1,同時設定“加鎖執行緒”是自己。最後,執行緒2自己從等待佇列裡出隊。

整個過程,參見下圖:

image.png

這個就是公平鎖的策略,過來加鎖的執行緒全部是按照先來後到的順序,依次進入等待佇列中排隊的,不會盲目的胡亂搶佔加鎖,非常的公平。

三.小結

好了,通過畫圖和文字分析,相信大家都明白什麼是公平鎖,什麼是非公平鎖了!

不過要知道java併發包裡很多鎖預設的策略都是非公平的,也就是可能後來的執行緒先加鎖,先來的執行緒後加鎖。

而一般情況下,非公平的策略都沒什麼大問題,但是大家要對這個策略做到心裡有數,在開發的時候,需要自己來考慮和權衡是要用公平策略還是非公平策略。

相關文章