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:原子更新長整型字段類;


免責聲明!

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



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