天天看點

過關斬将之路-Lock&AQS&ReentrantLock&ReentrantLock

作者:IT楓鬥者

Lock

1問:Lock和Synchroinzed有什麼差別?

1答:

  • synchronized沒有Lock靈活,Lock想在哪加就在哪加。比較靈活。
  • Lock需要手動釋放鎖(unlock)一定要放到finally去執行,因為不管你程式正常結束還是異常結束,都需要釋放鎖,免得造成鎖無法釋放,最終死鎖。synchronized會自動釋放鎖。
  • synchronized是關鍵字,Lock是接口。接口就意味着我們能随意發揮,建立自己想要的鎖。
  • Lock可以配合Condition條件完成更多的強大自定義功能。(IT楓鬥者怎麼樣)

2問:如何采用Lock接口自己實作一個鎖?

2答:

思路:

  • 搞個volatile狀态變量代表鎖重入次數
  • 然後CAS自旋這個狀态變量進行++操作
  • 幾次擷取鎖就要幾次釋放鎖,否則死鎖
  • 可重入的實作思路就是每次來的時候都判斷請求線程是不是目前持有鎖的線程,是的話,cas+volatile變量進行自增。不是的話wait阻塞。(IT楓鬥者怎麼樣)

代碼示例:

過關斬将之路-Lock&AQS&ReentrantLock&ReentrantLock

AQS

1問:談談你對AQS的了解?

1答:

AQS是一個鎖的基礎架構,巧妙地采取了模闆方法設計模式,比如 ReentrantLock、ReentrantReadWriteLock、Semaphore、CountDownLatch 等這些子類都是繼承這個模闆架構來實作自己的上鎖和釋放鎖的核心邏輯。它的原理就是cas一個volatile的state變量(加鎖狀态的變量)來結合FIFO來實作的。(IT楓鬥者怎麼樣)

2問:AQS用到了哪種設計模式?

2答:

模闆方法設計模式,比如:tryAcquire、tryRelease 和 tryAcquireShared、tryReleaseShared 都是直接抛出異常,具體實作交由子類。

3問:AQS如何實作獨占鎖和共享鎖?

3答:

  • Exclusive:獨占鎖
當鎖被頭節點擷取後,隻有頭節點擷取鎖,其餘節點的線程繼續沉睡,等待鎖被釋放後,才會喚醒下一個節點的線程。
  • Shared:共享鎖
隻要頭節點擷取鎖成功,就在喚醒自身節點對應的線程的同時,繼續喚醒AQS隊列中的下一個節點的線程,每個節點在喚醒自身的同時還會喚醒下一個節點對應的線程,以實作共享狀态的“向後傳播”,進而實作共享功能。

4問:AQS如何實作鎖重入?

4答:AQS不負責維護鎖重入,提供了tryAccquire模闆方法交由子類自己實作。

5問:AQS如何實作公平鎖和非公平鎖?

5答:AQS不負責實作是否公平,提供了tryAccquire模闆方法交由子類自己實作。(IT楓鬥者怎麼樣)

6問:說一說AQS的加鎖過程以及解鎖流程?

6答:

獨占鎖加鎖流程

1、調用自定義同步器的tryAcquire()嘗試直接去擷取資源,若成功則直接傳回。

2、沒成功,則addWaiter()将該線程加入等待隊列的尾部,并标記為獨占模式。

3、然後執行acquireQueued()使線程在等待隊列中休息,有機會時(會被unpark()喚醒)會去嘗試

擷取資源。擷取到資源後才傳回,若在整個等待過程中被中斷過,則傳回true,否則傳回false。

4、若線程在等待過程中被中斷過,他是不響應的。隻是擷取資源後才再進行自我中斷selfInterrupt(),将中斷補上。(IT楓鬥者怎麼樣)

過關斬将之路-Lock&AQS&ReentrantLock&ReentrantLock

獨占鎖解鎖流程

1、用unpark()喚醒等待隊列中最前邊的那個未放棄線程,這裡我們也用s來表示吧。

2、此時,再和acquireQueued()聯系起來,s被喚醒後,進入if (p == head && tryAcquire(arg))的判斷(即使p!=head也沒關系,它會再進入shouldParkAfterFailedAcquire()尋找一個安全點。

3、這裡既然s已經是等待隊列中最前邊的那個未放棄線程了,那麼通過shouldParkAfterFailedAcquire()的調整,s也必然會跑到head的next結點,下一次自旋p==head就成立啦),然後s把自己設定成head标杆結點,表示自己已經擷取到資源了,acquire()也傳回了。(IT楓鬥者怎麼樣)

過關斬将之路-Lock&AQS&ReentrantLock&ReentrantLock

共享鎖的流程和獨占鎖的差別在于

共享鎖擷取鎖成功的話,還會去喚醒自己的後續節點,一起來獲得該鎖。而獨占鎖隻能一個一個的,每次都隻是第二個節點有機會擷取資源,其餘的都在排隊。

ReentrantLock

1問:ReentrantLock和AQS的關系

1答:ReetrantLock是一個可重入的獨占鎖,主要有兩個特性,一個是支援公平鎖和非公平鎖,一個是可重入。AQS是個模闆架構,ReentrantLock可以算作AQS模闆架構的具體實作。(IT楓鬥者怎麼樣)

2問:ReentrantLock如何實作公平鎖和非公平鎖的?

2答:

公平鎖:多個線程申請擷取同一資源時,必須按照申請順序,依次擷取資源。

非公平鎖:資源釋放時,任何線程都有機會獲得資源,而不管其申請順序。

公平和非公平通過構造器傳入Boolean值來決定,true:公平鎖,false:非公平鎖,預設是非公平鎖。(IT楓鬥者怎麼樣)

公平鎖原理

FairSync繼承Sync,而Sync繼承AbstractQueuedSynchronizer。ReentrantLock調用lock方法,最終會調用sync的tryAcquire函數,擷取資源。FairSync的tryAcquire函數,目前線程隻有在隊列為空或者是隊首節點的時候,才能擷取資源,否則會被加入到阻塞隊列中。這是公平鎖實作的關鍵,先入先出,不可跳号。

過關斬将之路-Lock&AQS&ReentrantLock&ReentrantLock

非公平鎖原理

NoFairSync繼承Sync,ReentrantLock調用lock方法,最終會調用sync的tryAcquire函數,擷取資源。而NoFairSync的tryAcquire函數,會調用父類Sync的方法nofairTryAcquire函數。通過對比可以發現,如果線程會直接嘗試CAS操作擷取資源,而不管阻塞隊列中是否有先于其申請的線程。也就是相對于公平鎖來講擷取鎖的時候沒有 !hasQueuedPredecessors() 判斷。

過關斬将之路-Lock&AQS&ReentrantLock&ReentrantLock

3問:ReentrantLock如何做到可重入的?

3答:

不管是公平鎖還是非公平鎖,都支援鎖可重入,具體實作是通過CAS自旋AQS的state變量來完成的,加鎖時判斷是不是目前持有鎖的線程,如果是的話,則cas state+1,進行重入,釋放鎖的時候cas state-1,state為0的時候代表沒有線程持有鎖。(IT楓鬥者怎麼樣)

過關斬将之路-Lock&AQS&ReentrantLock&ReentrantLock

ReentrantReadWriteLock

1問:說一說ReentrantReadWriteLock?

1答:

讀寫鎖,利用一個int變量來表示兩種鎖,高16位代表讀鎖,低16位代表寫鎖。它允許多個線程同時讀共享變量,這樣在讀的時候就不會互斥,提高讀的效率。但是隻允許一個線程寫共享變量,當寫共享變量的時候也會阻塞讀的操作。(IT楓鬥者怎麼樣)

2答:

在擷取讀鎖時:

過關斬将之路-Lock&AQS&ReentrantLock&ReentrantLock

擷取寫鎖時:

過關斬将之路-Lock&AQS&ReentrantLock&ReentrantLock

3問:釋放鎖的流程?

3答:

讀鎖釋放步驟

過關斬将之路-Lock&AQS&ReentrantLock&ReentrantLock

寫鎖釋放步驟:

過關斬将之路-Lock&AQS&ReentrantLock&ReentrantLock
過關斬将之路-Lock&AQS&ReentrantLock&ReentrantLock

4問:如何實作鎖重入?

4答:

ReentrantReadWriteLock 的讀鎖是一把共享鎖,通過 ThreadLocalHoldCounter 來維護共享讀鎖的重入次數的。ThreadLocalHoldCounter 繼承了 ThreadLocal<HoldCounter> , HoldCounter 包含了線程id和重入次數(firstReaderHoldCount)。(IT楓鬥者怎麼樣)

5問:說一說讀寫鎖的升降級?

5答:

讀寫鎖不允許鎖更新,也就是不允許讀鎖更新為寫鎖,如果讀鎖還沒有釋放,此時擷取寫鎖,會導緻寫鎖永久等待,最終導緻相關線程都阻塞。但是允許鎖降級,寫鎖可以降級為讀鎖。在一個方法裡既有讀操作又有寫操作,需要鎖降級。比如如下:

過關斬将之路-Lock&amp;AQS&amp;ReentrantLock&amp;ReentrantLock
過關斬将之路-Lock&amp;AQS&amp;ReentrantLock&amp;ReentrantLock

6問:用過ReentrantReadWriteLock嗎?

6答:

實際業務中沒寫過,但是學習開源項目leaf的号段模式時候看到過。利用readLock提升高并發下的性能,然後再填充第二個buffer的時候采取writeLock來更改填充狀态,而沒有采取ReentrantLock全部互斥。(IT楓鬥者怎麼樣)

繼續閱讀