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