(十)JDK源碼分析之ReentrantLock流程總結

  • 建議
    希望讀者打開源碼對照看,或者有AQS源碼基礎存在一些疑惑的同學看,一定能有所收穫

  • lock
    1.調用AQS的acquire方法,先tryAcquire方法嘗試獲取鎖根據status的判斷。如果獲取鎖成功,那麼直接返回結束;如果嘗試獲取失敗,那麼需要將當前線程封裝爲Node對象,加入到同步隊列中。

    2.如果嘗試獲取鎖失敗,那麼在acquire繼續調用addWaiter方法將當前線程封裝爲Node對象,加入到同步隊列中,如果同步隊列頭結點沒有初始化,那麼先cas初始化頭結點,然後再cas將當前創建的Node加入到尾節點後面。

    3.將上面未獲取到的Node添加到阻塞隊列後,在acquire方法中,會繼續調用acquireQueued方法進行輪訓獲取鎖,開始自旋(死循環)。1.自旋嘗試獲取鎖是再次調用tryAcquire方法,但是嘗試獲取鎖是有條件的,必須當前Node的前面的Node是頭結點,纔會去tryAcquire,否則是在浪費,因爲你前面Node都沒獲取到鎖,你怎麼可能獲取的到呢。2.自旋最多一次執行兩次,如果第二次自旋還是沒獲取到鎖,那麼當前線程被掛起(LockSupport.park),一直阻塞,直到被喚醒。

    4.被喚醒時,其實老頭結點還在(注意,注意,注意)。被喚醒的節點,在獲取到了鎖之後,纔會真正把老頭結點釋放掉,然後將自己設置爲新的頭結點。

  • unlock
    1.調用release開始釋放鎖,調用tryRelease修改status值。比如將status值減一,將exclusiveOwnerThread也就是當前AQS中持有鎖的線程置爲null.
    2.在release方法中會調用unparkSuccessor方法進行喚醒(LockSupport.unpark)他的下一個節點,讓他從掛起的狀態中喚醒,再次自旋去獲取鎖。

  • 總結
    1.大概流程就是一個線程去獲取鎖,如果獲取成功,那麼結束,如果獲取失敗,那麼加在同步隊列中,然後自旋兩次,直到前驅節點釋放鎖喚醒後繼節點,被喚醒後繼續去獲取鎖。

    2 在實現是有公平鎖和非公平鎖之分,這個在tryAcquire中有體現。公平鎖,去嘗試獲取鎖會先調用hasQueuedPredecessors,判斷同步隊列中是否有等待的Node,如果有,那麼直接排隊去,不參與鎖的競爭;非公平鎖,就是即使你是排隊在同步隊列中第一個,那麼新加入的線程依然會和它競爭。也就是說在被前驅節點喚醒的線程,開始再次自旋獲取鎖時,公平鎖時,是一定可以獲取到鎖的,非公平鎖不一定能獲取到,可能在競爭激烈情況下,造成鎖獲取飢餓。

    3由於可重入鎖的特點,status的值可能會大於1,同一個線程可以獲取多次鎖,每次加1,也就是說一個線程,你可以調用多次lock,但是同時幾點對應調用多次unlock,因爲只要status>0,那麼還是那個線程持有鎖

    4.頭結點喚醒後繼節點的一些細節需要注意一下。對於公平鎖,在頭結點釋放鎖之後,後繼節點可以馬上獲取到鎖,將自己設置爲新的頭結點;對於非公平鎖,在頭結點釋放鎖,雖然喚醒了後繼節點,但是有新進來(非同步隊列中)的線程可能會搶到鎖,導致後繼節點獲取鎖失敗,再次被掛起,當新進來的線程釋放鎖時,會使用頭結點再次喚醒它的後繼節點(新進來的隊列直接就獲取到鎖了,沒在同步隊列中,所以不需要去做acquireQueued的這段邏輯,只要在釋放鎖時,再次喚醒頭結點的後繼節點即可)。

    5.公平鎖和非公平鎖的區別只是在入隊列前頭結點的下一個節點(新頭結點)會和非同步隊列的線程發生競爭,入隊列後是一樣的。

    6.糾正一句話,“頭結點是持有鎖的節點”。公平鎖時,這句話沒啥問題,非公平鎖時,持有鎖的節點可能是頭結點,也有可能是入隊前競爭的那個線程搶到了鎖。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章