CAS和自旋鎖(spin lock)


一、自旋鎖提出的背景

      由於在多處理器系統環境中有些資源因為其有限性,有時需要互斥訪問(mutual exclusion),這時會引入鎖的機制,只有獲取了鎖的進程才能獲取資源訪問。即是每次只能有且只有一個進程能獲取鎖,才能進入自己的臨界區,同一時間不能兩個或兩個以上進程進入臨界區,當退出臨界區時釋放鎖。設計互斥算法時總是會面臨一種情況,即沒有獲得鎖的進程怎么辦?通常有2種處理方式。一種是沒有獲得鎖的調用者就一直循環在那里看是否該自旋鎖的保持者已經釋放了鎖,這就是自旋鎖,他不用將線城阻塞起來(NON-BLOCKING);另一種是沒有獲得鎖的進程就阻塞(BLOCKING)自己,請求OS調度另一個線程上處理器,這就是互斥鎖。

 

二、自旋鎖原理

      跟互斥鎖一樣,一個執行單元要想訪問被自旋鎖保護的共享資源,必須先得到鎖,在訪問完共享資源后,必須釋放鎖。如果在獲取自旋鎖時,沒有任何執行單元保持該鎖,那么將立即得到鎖;如果在獲取自旋鎖時鎖已經有保持者,那么獲取鎖操作將自旋在那里,直到該自旋鎖的保持者釋放了鎖。由此我們可以看出,自旋鎖是一種比較低級的保護數據結構或代碼片段的原始方式,這種鎖可能存在兩個問題:

  • 遞歸死鎖:試圖遞歸地獲得自旋鎖必然會引起死鎖:遞歸程序的持有實例在第二個實例循環,以試圖獲得相同自旋鎖時,不會釋放此自旋鎖。在遞歸程序中使用自旋鎖應遵守下列策略:遞歸程序決不能在持有自旋鎖時調用它自己,也決不能在遞歸調用時試圖獲得相同的自旋鎖。此外如果一個進程已經將資源鎖定,那么,即使其它申請這個資源的進程不停地瘋狂“自旋”,也無法獲得資源,從而進入死循環。
  • 過多占用cpu資源。如果不加限制,由於申請者一直在循環等待,因此自旋鎖在鎖定的時候,如果不成功,不會睡眠,會持續的嘗試,單cpu的時候自旋鎖會讓其它process動不了. 因此,一般自旋鎖實現會有一個參數限定最多持續嘗試次數. 超出后, 自旋鎖放棄當前time slice. 等下一次機會

     由此可見,自旋鎖比較適用於鎖使用者保持鎖時間比較短的情況。正是由於自旋鎖使用者一般保持鎖時間非常短,因此選擇自旋而不是睡眠是非常必要的,自旋鎖的效率遠高於互斥鎖。

     鎖通常有兩個主要方法lock(),unlock()。通常是按照如下范式來使用鎖

1 Lock mutex = new LockImpl();
2 //...
3 mutex.lock();
4 try {
5     //... do some work
6 } finally {
7     mutex.unlock();
8 }

 

三、Java CAS

      CAS是一種系統原語(所謂原語屬於操作系統用語范疇。原語由若干條指令組成的,用於完成一定功能的一個過程。primitive or atomic action 是由若干個機器指令構成的完成某種特定功能的一段程序,具有不可分割性·即原語的執行必須是連續的,在執行過程中不允許被中斷)。CAS是Compare And Set的縮寫。CAS有3個操作數,內存值V,舊的預期值A,要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改為B,否則什么都不做。

     在x86 平台上,CPU提供了在指令執行期間對總線加鎖的手段。CPU芯片上有一條引線#HLOCK pin,如果匯編語言的程序中在一條指令前面加上前綴"LOCK",經過匯編以后的機器代碼就使CPU在執行這條指令的時候把#HLOCK pin的電位拉低,持續到這條指令結束時放開,從而把總線鎖住,這樣同一總線上別的CPU就暫時不能通過總線訪問內存了,保證了這條指令在多處理器環境中的原子性

      sun.misc.Unsafe是JDK里面的一個內部類,這個類為JDK嚴格保護,因為他提供了大量的低級的內存操作和系統功能。如果因為錯誤的使用了這個類,不會有“異常”被扔出,甚至會造成JVM宕機。這也是為什么這個類的名字是Unsafe的原因。因此當使用這個類的時候,你一定要明白你在干什么。這個類中提供了3個CAS的操作

方法名 解釋
compareAndSwapInt(Object object, long address, int expected, int newValue)  比較對象object的某個int型的屬性(以地址的方式訪問),如果他的數據值是expected,則設定為newValue,返回true;否則返回false
compareAndSwapLong(Object object, long address, long expected, long newValue)  比較對象object的某個long型的屬性(以地址的方式訪問),如果他的數據值是expected,則設定為newValue,返回true;否則返回false
compareAndSwapLong(Object object, long address, Object expected, Object newValue)  比較對象object的某個Object型的屬性(以地址的方式訪問),如果他的數據值是expected,則設定為newValue,返回true;否則返回false

 

四、Java自旋鎖應用-原子包

Jdk1.5以后,提供了java.util.concurrent.atomic包,這個包里面提供了一組原子類。其基本的特性就是在多線程環境下,當有多個線程同時執行這些類的實例包含的方法時,具有排他性,即當某個線程進入方法,執行其中的指令時,不會被其他線程打斷,而別的線程就像自旋鎖一樣,一直等到該方法執行完成,才由JVM從等待隊列中選擇一個另一個線程進入,這只是一種邏輯上的理解。實際上是借助硬件的相關指令來實現的,不會阻塞線程(或者說只是在硬件級別上阻塞了)。其中的類可以分成4組

  • AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
  • AtomicIntegerArray,AtomicLongArray
  • AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
  • AtomicMarkableReference,AtomicStampedReference,AtomicReferenceArray

 

我們來看一段AtomicBoolean中的自旋鎖的代碼

public final boolean getAndSet(boolean newValue) {
   for (;;) {
       boolean current = get();
       if (compareAndSet(current, newValue))
           return current;
   }
}

 

 

參考

http://baike.baidu.com/view/1250961.htm?fr=aladdin


免責聲明!

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



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