ReentrantLock 如何實現非公平鎖?和公平鎖實現有什麼區別

reentrant 英[riːˈɛntrənt] 美[ˌriˈɛntrənt] 先學會讀。單詞原意是可重入的html

  1. 考察顯示鎖的使用。可延伸知識點
    1. 獨佔鎖 & 共享鎖
      1. 獨佔鎖 - 悲觀鎖(不能同時被多個線程持有 - synchronized鎖 & ReentrantLock)
      2. 共享鎖 - 樂觀鎖(ReentrantReadLock )
      3. 讀共享、寫排他
    2. 重入鎖  
      1. 方法進行深層次調用時,獲取同一把鎖可以獲取到,不會死鎖  
    3. 使用範式
      1. finally 
        複製代碼
                Lock lock = new ReentrantLock();
                lock.lock();
                try{
                    //一頓操做
                }finally {
                    lock.unlock();
                }    
        複製代碼
    4. CAS原理實現樂觀鎖
      1. 原理 : 在線程對數據進行修改時,須要對比如今持有的變量和原始地址中的值是否相同,若相同則替換成功,若不一樣替換失敗
      2. 實現樂觀鎖 = 自旋 + CAS
      3. 問題 :
        1. ABA問題 - jdk AtomicStampedReference  AtomicMarkableReference
        2. 自旋時間過長會致使CPU消耗過大
        3. 一次操做只能修改一個內存地址的變量 AtomicReference<V>
      4. jdk相關實現類
        1. AtomicInteger  
        2. AtomicReference<V>  多個共享變量一塊兒操做
        3. AtomicReferenceArray  操做時是複製了原數組一份,修改後原數組的值不變
        4. AtomicMarkableReference 解決ABA問題,但只關心是否改變
        5. AtomicStampedReference 解決ABA問題,會記錄改變次數.可經過getStamp()獲取
    5. condition - wait&notify --> ***
    6. CHL隊列鎖 - 基於鏈表
      1. 每一個線程拿鎖時建立一個Node,locked狀態置爲true,把本身放到鏈表的tail,而後把myPred指向以前的tail,以後向前循環check locked狀態,直到爲false時本身就拿到了鎖
    7. AQS - 抽象隊列同步器
      1. CHL實現的變種 -- 雙向鏈表
      2. 實現多線程訪問共享資源的同步框架FIFO
      3. 採用模板方法,一些方法須要繼承者實現,但爲何不設計成abstract方法(抽象方法都要實現,爲開發者考慮,獨佔式獲取鎖只實現獨佔方法,共享方式只實現共享方法)
        1. 獨佔式 tryAcquire、 tryRelease、isHeldExclusively
        2. 共享 tryAcquireSharedtryReleaseSharedisHeldExclusively
        3. 方法都有一個參數 ?
      4. state屬性
        1. volatile int state 表明共享資源
        2. 提供三種訪問方式 getState()、setState()、compareAndSetState() 
          1. setState 和 compareAndSetState 有什麼區別 ? 前者不是有安全問題嗎,爲何還存在。(setState 是在已經拿到鎖的狀況下調用,不會有安全問題)
        3. ReentrantLock 用來標記拿鎖的次數、CountDownLatch 用來標記任務的個數
      5. acquire()方法
        1. tryAcquire()
        2. addWaiter() 默認獨佔式 - 自旋添加節點
        3. acquireQueued() 真正的拿鎖方法 返回等待過程當中是否被中斷過,自旋+阻塞獲取資源 
          1. 若前驅結點是head且拿到了鎖的狀況下,把當前節點置爲head節點,並把原head節點脫離
          2. shouldParkAfterFailedAcquire(Node, Node) 返回前驅節點是否處於等待狀態Node.SIGNAL。並將本身放在此節點的後置節點
          3. parkAndCheckInterrupt() 返回是否被中斷過。阻塞當前線程,若是線程被喚醒,檢查是被打斷仍是被正常喚醒
        4. 先去嘗試拿鎖,拿不到就將本身放在等待隊列尾部,而後自旋向前尋找,直到head節點拿到鎖爲止。即便中間被打算,等待過程也不會中斷。而是在拿到鎖以後再中斷本身
      6. release()方法
        1. tryRelease(arg)
        2. 釋放鎖成功以後調用 unparkSuccessor(head) -> 將head節點狀態置爲0,用unpark()喚醒等待隊列中最後邊的那個未放棄線程
        3. 喚醒acquireQueued方法中的判斷,讓線程拿到鎖
      7. acquireShared()方法
        1. 相似acquire()  
        2. 多一個setHeadAndPropagate()方法。在拿到資源的時候向後喚醒 - 體現共享
        3. 調用了doReleaseShared()
      8. releaseShared()
        1.  doReleaseShared()釋放掉資源後,喚醒後繼  實際調用unparkSuccessor(head)
    8. 公平鎖&非公平鎖
      1. 遵循拿鎖的順序
    9. 隱式鎖synchronized 對比
      1. lock提供一些除lock()操做以外功能,更加靈活
      2. 非特殊狀況下使用synchronized,jdk對它的優化比較大
    10. 模板方法
  2. 實現
    1. ReentrantLock自身並未繼承AQS,而是採用內部類Sync繼承。屏蔽內部實現、外部調用者不用關心具體細節
    2. 如何實現可重入
      1. tryAcquire if(state ==0 )的else中進行當前線程鎖的累加
    3. 公平鎖和非公平鎖有什麼區別
      1. 非公平鎖再獲取鎖的時候不按排隊順序而是隨機拿鎖 
      2. tryAcquire方法中!hasQueuedPredecessors() 來斷定隊列中是否有前驅節點在等待鎖
    4. 在阻塞以前,線程都會經過shouldParkAfterFailedAcquire去修改其前驅節點的waitStatus=-1。這是爲何?爲了release時unparkSuccessor(head) 喚醒後續節點
    5. unparkSuccessor時爲何會出現s==null || s.waitStatus>0的狀況,這種狀況下,爲何要經過prev指針反向查找Successor節點?
      1. s == null 是由於acquireQueued() 在拿到鎖以後會將head.next = null .這樣鏈表就斷了,因此要從尾部節點向前找
      2. s > 0 是在cancel的時候,節點在head節點的後繼節點斷開。致使鏈表斷裂。  

總結: 如何回答這個問題。數組

1. ReentrantLock 經過定義一個內部類來實現Lock接口,公平鎖和非公平鎖的實現都是基於這個繼承自AQS的內部類Sync。不一樣的點是在拿鎖的時候,非公平鎖會直接拿鎖,而公平鎖會判斷隊列中是否還有線程在等待鎖。若是有就會拿鎖失敗。安全

2.後面就是回答AQS原理了多線程

 

參考AQS http://www.javashuo.com/article/p-xcevmtwv-gz.html    框架

圖解 https://www.jianshu.com/p/b6efbdbdc6fa優化

相關文章
相關標籤/搜索