1. 程式人生 > 其它 >JUC原子操作類與樂觀鎖CAS

JUC原子操作類與樂觀鎖CAS

JUC原子操作類與樂觀鎖CAS

​ 硬體中存在併發操作的原語,從而在硬體層面提升效率。在intel的CPU中,使用cmpxchg指令。在Java發展初期,java語言是不能夠利用硬體提供的這些便利來提升系統的效能的。而隨著java不斷的發展,Java本地方法(JNI)的出現,使得java程式越過JVM直接呼叫本地方法提供了一種便捷的方式。

樂觀鎖悲觀鎖

悲觀鎖

​ 總是假設最壞的情況,每次去拿資料的時候都認為別人會修改,所以每次在拿資料的時候都會上鎖,這樣別人想拿這個資料就會阻塞直到它拿到鎖(共享資源每次只給一個執行緒使用,其它執行緒阻塞,用完後再把資源轉讓給其它執行緒)。傳統的關係型資料庫裡邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。Java中synchronized和ReentrantLock等獨佔鎖就是悲觀鎖思想的實現。

樂觀鎖

​ 總是假設最好的情況,每次去拿資料的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個資料,可以使用版本號機制和CAS演算法實現。樂觀鎖適用於多讀的應用型別,這樣可以提高吞吐量,像資料庫提供的類似於write_condition機制,其實都是提供的樂觀鎖。在Java中java.util.concurrent.atomic包下面的原子變數類就是使用了樂觀鎖CAS實現的。

兩種鎖的使用場景

​ 像樂觀鎖適用於寫比較少的情況下(多讀場景)。但如果是多寫的情況,一般會經常產生衝突,這就會導致上層應用會不斷的進行retry,這樣反倒是降低了效能,所以一般多寫的場景下用悲觀鎖就比較合適。

樂觀鎖CAS

​ CAS 操作包含三個運算元 —— 記憶體位置(V)、預期原值(A)和新值(B)。 如果記憶體位置的值與預期原值相匹配,那麼處理器會自動將該位置值更新為新值 。否則,處理器不做任何操作。無論哪種情況,它都會在 CAS 指令之前返回該 位置的值。(在 CAS 的一些特殊情況下將僅返回 CAS 是否成功,而不提取當前值。)

CAS雖然很高效地解決了原子操作,但是CAS仍然存在三個問題
1)ABA問題。因為CAS需要在操作值的時候,檢查值有沒有發生變化,如果沒有發生變化則更新,但是如果一個值原來是A,變成了B,又變成了A,那麼使用CAS進行檢查時會發現它的值沒有發生變化,但是實際上卻變化了。ABA問題的解決思路就是使用版本號。在變前面追加上版本號,每次變數更新的時候把版本號加1,那麼A→B→A就會變成1A→2B→3A。從Java 1.5開始,JDK的Atomic包裡提供了一個類AtomicStampedReference來解決ABA問題。這個類的compareAndSet方法的作用是首先檢查當前引用是否等於預期引用,並且檢查當前標誌是否等於預期標誌,如果全部相等,則以原子方式將該引用和該標誌的值設定為給定的更新值。

2)迴圈時間長開銷大。自旋CAS如果長時間不成功,會給CPU帶來非常大的執行開銷。

3)只能保證一個共享變數的原子操作。當對一個共享變數執行操作時,我們可以使用迴圈CAS的方式來保證原子操作,但是對多個共享變數操作時,迴圈CAS就無法保證操作的原子性。

這個時候就可以用鎖。還有一個取巧的辦法,就是把多個共享變數合併成一個共享變數來操作。比如,有兩個共享變數i=2,j=a,合併一下ij=2a,然後用CAS來操作ij。從Java 1.5開始,JDK提供了AtomicReference類來保證引用物件之間的原子性,就可以把多個變數放在一個物件裡來進行CAS操作。

JUC原子操作類

而Java從JDK 1.5開始提供了java.util.concurrent.atomic包(以下簡稱Atomic包),這個包中的原子操作類提供了一種用法簡單、效能高效、執行緒安全地更新一個變數的方式。大致可以屬於4種類型的原子更新方式,分別是原子更新基本型別、原子更新陣列、原子更新引用和原子更新屬性(欄位)。Atomic包裡的類基本都是使用Unsafe實現的包裝類。

更新基本型別

使用原子的方式更新基本型別,Atomic包提供了以下3個類。

AtomicBoolean:原子更新布林型別。
AtomicInteger:原子更新整型。
AtomicLong:原子更新Long型別。

原子更新陣列型別

AtomicIntegerArray:原子更新整型陣列中的元素;
AtomicLongArray:原子更新長整型陣列中的元素;
AtomicReferenceArray:原子更新引用型別陣列中的元素

原子更新引用型別

原子更新基本型別的AtomicInterger,只能更新一個變數,如果要原子更新多個變數,就需要使用這個原子更新引用型別提供的類。

AtomicReference:原子更新引用型別
AtomicStampReference:原子更新引用型別,這種更新方式會帶有版本號。而為什麼在更新的時候會帶有版本號,是為了解決CAS的ABA問題。
AtomicMarkableReference:原子更新帶有標記位的引用型別。可以原子更新一個布林型別的標記位和引用型別。構造方法是

原子更新欄位型別

如果需要更新物件的某個欄位,並在多執行緒的情況下,能夠保證執行緒安全,atomic提供了相應的原子操作類:

AtomicIntegeFieldUpdater:原子更新整型欄位類;
AtomicLongFieldUpdater:原子更新長整型欄位類;

未經作者同意請勿轉載

本文來自部落格園作者:aixueforever,原文連結:https://www.cnblogs.com/aslanvon/p/15127700.html