1. 程式人生 > >並發編程--CAS自旋鎖

並發編程--CAS自旋鎖

cpu 獲取鎖 獲得 false 大量 可見性 這也 java 高效

在前兩篇博客中我們介紹了並發編程--volatile應用與原理和並發編程--synchronized的實現原理(二),接下來我們介紹一下CAS自旋鎖相關的知識。

一、自旋鎖提出的背景

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

二、自旋鎖原理

跟互斥鎖一樣,一個執行單元要想訪問被自旋鎖保護的共享資源,必須先得到鎖,在訪問完共享資源後,必須釋放鎖。如果在獲取自旋鎖時,沒有任何執行單元保持該鎖,那麽將立即得到鎖;如果在獲取自旋鎖時鎖已經有保持者,那麽獲取鎖操作將自旋在那裏,直到該自旋鎖的保持者釋放了鎖。由此我們可以看出,自旋鎖是一種比較低級的保護數據結構或代碼片段的原始方式,這種鎖可能存在兩個問題:
遞歸死鎖:試圖遞歸地獲得自旋鎖必然會引起死鎖:遞歸程序的持有實例在第二個實例循環,以試圖獲得相同自旋鎖時,不會釋放此自旋鎖。在遞歸程序中使用自旋鎖應遵守下列策略:遞歸程序決不能在持有自旋鎖時調用它自己,也決不能在遞歸調用時試圖獲得相同的自旋鎖。此外如果一個進程已經將資源鎖定,那麽,即使其它申請這個資源的進程不停地瘋狂“自旋”,也無法獲得資源,從而進入死循環。
過多占用cpu資源。如果不加限制,由於申請者一直在循環等待,因此自旋鎖在鎖定的時候,如果不成功,不會睡眠,會持續的嘗試,單cpu的時候自旋鎖會讓其它process動不了. 因此,一般自旋鎖實現會有一個參數限定最多持續嘗試次數. 超出後, 自旋鎖放棄當前time slice. 等下一次機會
由此可見,自旋鎖比較適用於鎖使用者保持鎖時間比較短的情況。正是由於自旋鎖使用者一般保持鎖時間非常短,因此選擇自旋而不是睡眠是非常必要的,自旋鎖的效率遠高於互斥鎖。

三、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

五、CAS實現原子操作的三大問題

在Java並發包中有一些並發框架也使用了自旋CAS的方式來實現原子操作,比如LinkedTransferQueue類的Xfer方法。CAS雖然很高效地解決了原子操作,但是CAS仍然存在三大問題。ABA問題,循環時間長開銷大,以及只能保證一個共享變量的原子操作。
1)ABA問題。因為CAS需要在操作值的時候,檢查值有沒有發生變化,如果沒有發生變化則更新,但是如果一個值原來是A,變成了B,又變成了A,那麽使用CAS進行檢查時會發現它的值沒有發生變化,但是實際上卻變化了。ABA問題的解決思路就是使用版本號。在變量前面追加上版本號,每次變量更新的時候把版本號加1,那麽A→B→A就會變成1A→2B→3A。從Java 1.5開始,JDK的Atomic包裏提供了一個類AtomicStampedReference來解決ABA問題。這個類的compareAndSet方法的作用是首先檢查當前引用是否等於預期引用,並且檢查當前標誌是否等於預期標誌,如果全部相等,則以原子方式將該引用和該標誌的值設置為給定的更新值。
public boolean compareAndSet(
V expectedReference, // 預期引用
V newReference, // 更新後的引用
int expectedStamp, // 預期標誌
int newStamp // 更新後的標誌
)
2)循環時間長開銷大。自旋CAS如果長時間不成功,會給CPU帶來非常大的執行開銷。如果JVM能支持處理器提供的pause指令,那麽效率會有一定的提升。pause指令有兩個作用:第一,它可以延遲流水線執行指令(de-pipeline),使CPU不會消耗過多的執行資源,延遲的時間取決於具體實現的版本,在一些處理器上延遲時間是零;第二,它可以避免在退出循環的時候因內存順序沖突(Memory Order Violation)而引起CPU流水線被清空(CPU Pipeline Flush),從而提高CPU的執行效率。
3)只能保證一個共享變量的原子操作。當對一個共享變量執行操作時,我們可以使用循環CAS的方式來保證原子操作,但是對多個共享變量操作時,循環CAS就無法保證操作的原子性,這個時候就可以用鎖。還有一個取巧的辦法,就是把多個共享變量合並成一個共享變量來操作。比如,有兩個共享變量i=2,j=a,合並一下ij=2a,然後用CAS來操作ij。從Java 1.5開始,JDK提供了AtomicReference類來保證引用對象之間的原子性,就可以把多個變量放在一個對象裏來進行CAS操作。

六、volatile和synchronized的區別

volatile本質是在告訴jvm當前變量在寄存器(工作內存)中的值是不確定的,需要從主存中讀取; synchronized則是鎖定當前變量,只有當前線程可以訪問該變量,其他線程被阻塞住。
volatile僅能使用在變量級別;synchronized則可以使用在變量、方法、和類級別的
volatile僅能實現變量的修改可見性,不能保證原子性;而synchronized則可以保證變量的修改可見性和原子性
volatile不會造成線程的阻塞;synchronized可能會造成線程的阻塞。
volatile標記的變量不會被編譯器優化;synchronized標記的變量可以被編譯器優化


參考:

http://www.mamicode.com/info-detail-334916.html

http://blog.csdn.net/suifeng3051/article/details/52611233

並發編程--CAS自旋鎖