年底了我裁完兄弟自己也離職了,復習了Java鎖的底層準備面試...
一、寫在前面
上篇文章:《??SpringBoot3.0都正式發布了,嘗鮮之前先搞明白AQS底層再說??》聊了一下java并發包中的AQS的工作原理,也間接說明了ReentrantLock的工作原理。
這篇文章接著來聊一個話題,java并發包中的公平鎖與非公平鎖有啥區別?
二、什么是非公平鎖?
先來聊聊非公平鎖是啥,現在大家先回過頭來看下面這張圖。
如上圖,現在線程1加了鎖,然后線程2嘗試加鎖,失敗后進入了等待隊列,處于阻塞中。然后線程1釋放了鎖,準備來喚醒線程2重新嘗試加鎖。
注意一點,此時線程2可還停留在等待隊列里啊,還沒開始嘗試重新加鎖呢!
然而,不幸的事情發生了,這時半路殺出個程咬金,來了一個線程3!線程3突然嘗試對ReentrantLock發起加鎖操作,此時會發生什么事情?
很簡單!線程2還沒來得及重新嘗試加鎖呢。也就是說,還沒來得及嘗試重新執行CAS操作將state的值從0變為1呢!線程3沖上來直接一個CAS操作,嘗試將state的值從0變為1,結果還成功了!
一旦CAS操作成功,線程3就會將“加鎖線程”這個變量設置為他自己。給大家來一張圖,看看這整個過程:
明明人家線程2規規矩矩的排隊領鎖呢,結果你線程3不守規矩,線程1剛釋放鎖,不分青紅皂白,直接就跑過來搶先加鎖了。
這就導致線程2被喚醒過后,重新嘗試加鎖執行CAS操作,結果毫無疑問,失敗!
原因很簡單啊!因為加鎖CAS操作,是要嘗試將state從0變為1,結果此時state已經是1了,所以CAS操作一定會失敗!
一旦加鎖失敗,就會導致線程2繼續留在等待隊列里不斷的等著,等著線程3釋放鎖之后,再來喚醒自己,真是可憐!先來的線程2居然加不到鎖!
同樣給大家來一張圖,體會一下線程2這無助的過程:
上述的鎖策略,就是所謂的非公平鎖!
如果你用默認的構造函數來創建ReentrantLock對象,默認的鎖策略就是非公平的。
在非公平鎖策略之下,不一定說先來排隊的線程就就先會得到機會加鎖,而是出現各種線程隨意搶占的情況。
那如果要實現公平鎖的策略該怎么辦呢?也很簡單,在構造ReentrantLock對象的時候傳入一個true即可:
ReentrantLock lock = new ReentrantLock(true)。
此時就是說讓他使用公平鎖的策略,那么公平鎖具體是什么意思呢?
三、什么是公平鎖?
咱們重新回到第一張圖,就是線程1剛剛釋放鎖之后,線程2還沒來得及重新加鎖的那個狀態。
同樣,這時假設來了一個線程3,突然殺出來,想要加鎖。
如果是公平鎖的策略,那么此時線程3不會跟個愣頭青一樣盲目的直接加鎖。
他會先判斷一下:咦?AQS的等待隊列里,有沒有人在排隊啊?如果有人在排隊的話,說明我前面有兄弟正想要加鎖啊!
如果AQS的隊列里真的有線程排著隊,那我線程3就不能跟個二愣子一樣直接搶占加鎖了。
因為現在咱們是公平策略,得按照先來后到的順序依次排隊,誰先入隊,誰就先從隊列里出來加鎖!
所以,線程3此時一判斷,發現隊列里有人排隊,自己就會乖乖的排到隊列后面去,而不會貿然加鎖!
同樣,整個過程我們用下面這張圖給大家直觀的展示一下:
上面的等待隊列中,線程3會按照公平原則直接進入隊列尾部進行排隊。
接著,線程2不是被喚醒了么?他就會重新嘗試進行CAS加鎖,此時沒人跟他搶,他當然可以加鎖成功了。
然后呢,線程2就會將state值變為1,同時設置“加鎖線程”是自己。最后,線程2自己從等待隊列里出隊。
整個過程,參見下圖:
這個就是公平鎖的策略,過來加鎖的線程全部是按照先來后到的順序,依次進入等待隊列中排隊的,不會盲目的胡亂搶占加鎖,非常的公平。
四、小結
好了,通過畫圖和文字分析,相信大家都明白什么是公平鎖,什么是非公平鎖了!
不過要知道java并發包里很多鎖默認的策略都是非公平的,也就是可能后來的線程先加鎖,先來的線程后加鎖。
而一般情況下,非公平的策略都沒什么大問題,但是大家要對這個策略做到心里有數,在開發的時候,需要自己來考慮和權衡是要用公平策略還是非公平策略。