Java 並發編程:AQS 的自旋鎖


互斥鎖在AQS的互斥鎖與共享鎖中已經做了詳細介紹,一個鎖一次只能由一個線程持有,其它線程則無法獲得,除非已持有鎖的線程釋放了該鎖。這里為什么提互斥鎖呢?其實互斥鎖和自旋鎖都是實現同步的方案,最終實現的效果都是相同的,但它們對未獲得鎖的線程的處理方式卻是不同的。對於互斥鎖,當某個線程占有鎖后,另外一個線程將進入阻塞狀態。與互斥鎖類似,自旋鎖保證了公共數據在任意時刻最多只能由一條線程獲取使用,不同的是在獲取鎖失敗后自旋鎖會采取自旋的處理方式。

 

自旋鎖

自旋鎖是一種非阻塞鎖,它的核心機制就在自旋兩個字,即用自旋操作來替代阻塞操作。某一線程嘗試獲取某個鎖時,如果該鎖已經被另一個線程占用的話,則此線程將不斷循環檢查該鎖是否被釋放,而不是讓此線程掛起或睡眠。一旦另外一個線程釋放該鎖后,此線程便能獲得該鎖。自旋是一種忙等待狀態,過程中會一直消耗CPU的時間片。

 

為什么自旋

互斥鎖有一個很大的缺點,即獲取鎖失敗后線程會進入睡眠或阻塞狀態,這個過程會涉及到用戶態到內核態的調度,上下文切換的開銷比較大。假如某個鎖的鎖定時間很短,此時如果鎖獲取失敗則讓它睡眠或阻塞的話則有點得不償失,因為這種開銷可能比自旋的開銷更大。總結起來就是互斥鎖更適合持有鎖時間長的情況,而自旋鎖更適合持有鎖時間短的情況。

 

自旋鎖特點

  • 自旋鎖的核心機制就是死等,所有想要獲得鎖的線程都在不停嘗試去獲取鎖,當然這也會引來競爭問題。

  • 與互斥鎖一樣,自旋鎖也只允許一個線程獲得鎖。

  • 自旋鎖能提供中斷機制,因為它並不會進入阻塞狀態,所以能很好支持中斷。

  • 自旋鎖適用於鎖持有時間叫短的場景,即鎖保護臨界區很小的常見,這個很容易理解,如果持有鎖太久,那么將可能導致大量線程都在自旋,浪費大量CPU資源。

  • 自旋鎖無法保證公平性,不保證先到先獲得鎖,這樣就可能造成線程飢餓。

  • 自旋鎖需要保證各個本地緩存數據的一致性,在多處理器機器上,每個線程對應的處理器都對同一個變量進行讀寫。每次寫操作都需要同步每個處理器緩存,這可能會影響性能。

 

自旋鎖例子

下面看一個簡單的自旋鎖的實現,主要看lock和unlock兩個方法,Unsafe僅僅是為操作提供了硬件級別的原子CAS操作。對於lock方法,假如有若干線程競爭,能成功通過CAS操作修改value值為newV的線程即是成功獲取鎖的線程。它將順利通過,而其它線程則不斷在循環檢測value值是否改回0,將value改為0的操作就是獲取鎖的線程執行完后對該鎖進行釋放。對於unlock方法,用於釋放鎖,釋放后若干線程又繼續對該鎖競爭。如此一來,沒獲得鎖的線程也不會被掛起或阻塞,而是不斷循環檢查狀態。

AQS的自旋機制

AQS框架中不管是互斥鎖還是共享鎖實現的基礎思想都是基於自旋的機制,不過它對自旋鎖做了優化,這個后面會繼續講解。比如下面兩圖為AQS框架獲取獨占鎖和共享鎖的邏輯,具體的邏輯我們先不用管,主要關注方框框住的for(;;)這行代碼。這便是自旋操作,通過無限循環來實現自旋

 

 

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM