互斥鎖在 AQS的互斥鎖與共享鎖 中已經做了詳細介紹,一個鎖一次隻能由一個線程持有,其它線程則無法獲得,除非已持有鎖的線程釋放了該鎖。這裡為什麼提互斥鎖呢?其實互斥鎖和自旋鎖都是實作同步的方案,最終實作的效果都是相同的,但它們對未獲得鎖的線程的處理方式卻是不同的。對于互斥鎖,當某個線程占有鎖後,另外一個線程将進入阻塞狀态。與互斥鎖類似,自旋鎖保證了公共資料在任意時刻最多隻能由一條線程擷取使用,不同的是在擷取鎖失敗後自旋鎖會采取自旋的處理方式。
自旋鎖
自旋鎖是一種非阻塞鎖,它的核心機制就在自旋兩個字,即用自旋操作來替代阻塞操作。某一線程嘗試擷取某個鎖時,如果該鎖已經被另一個線程占用的話,則此線程将不斷循環檢查該鎖是否被釋放,而不是讓此線程挂起或睡眠。一旦另外一個線程釋放該鎖後,此線程便能獲得該鎖。自旋是一種忙等待狀态,過程中會一直消耗CPU的時間片。
為什麼自旋
互斥鎖有一個很大的缺點,即擷取鎖失敗後線程會進入睡眠或阻塞狀态,這個過程會涉及到使用者态到核心态的排程,上下文切換的開銷比較大。假如某個鎖的鎖定時間很短,此時如果鎖擷取失敗則讓它睡眠或阻塞的話則有點得不償失,因為這種開銷可能比自旋的開銷更大。總結起來就是互斥鎖更适合持有鎖時間長的情況,而自旋鎖更适合持有鎖時間短的情況。
自旋鎖特點
- 自旋鎖的核心機制就是死等,所有想要獲得鎖的線程都在不停嘗試去擷取鎖,當然這也會引來競争問題。
- 與互斥鎖一樣,自旋鎖也隻允許一個線程獲得鎖。
- 自旋鎖能提供中斷機制,因為它并不會進入阻塞狀态,是以能很好支援中斷。
- 自旋鎖适用于鎖持有時間叫短的場景,即鎖保護臨界區很小的常見,這個很容易了解,如果持有鎖太久,那麼将可能導緻大量線程都在自旋,浪費大量CPU資源。
- 自旋鎖無法保證公平性,不保證先到先獲得鎖,這樣就可能造成線程饑餓。
- 自旋鎖需要保證各個本地緩存資料的一緻性,在多處理器機器上,每個線程對應的處理器都對同一個變量進行讀寫。每次寫操作都需要同步每個處理器緩存,這可能會影響性能。
自旋鎖例子
下面看一個簡單的自旋鎖的實作,主要看lock和unlock兩個方法,Unsafe僅僅是為操作提供了硬體級别的原子CAS操作。對于lock方法,假如有若幹線程競争,能成功通過CAS操作修改value值為newV的線程即是成功擷取鎖的線程。它将順利通過,而其它線程則不斷在循環檢測value值是否改回0,将value改為0的操作就是擷取鎖的線程執行完後對該鎖進行釋放。對于unlock方法,用于釋放鎖,釋放後若幹線程又繼續對該鎖競争。如此一來,沒獲得鎖的線程也不會被挂起或阻塞,而是不斷循環檢查狀态。
AQS的自旋機制
AQS架構中不管是互斥鎖還是共享鎖實作的基礎思想都是基于自旋的機制,不過它對自旋鎖做了優化,這個後面會繼續講解。比如下面兩圖為AQS架構擷取獨占鎖和共享鎖的邏輯,具體的邏輯我們先不用管,主要關注方框框住的for(;;)這行代碼。這便是自旋操作,通過無限循環來實作自旋
Java 并發程式設計完整版