欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java中CAS機制實現(xiàn)方法詳解

 更新時間:2024年09月24日 08:26:54   作者:Java技術債務  
傳統(tǒng)的并發(fā)控制手段如synchronized和ReentrantLock雖有效防止資源競爭,卻可能引起性能開銷,相比之下,CAS(CompareAndSwap)提供一種輕量級的樂觀鎖策略,通過硬件級別的原子指令實現(xiàn)無鎖并發(fā),提高性能,需要的朋友可以參考下

概述

傳統(tǒng)的并發(fā)控制手段,如使用synchronized關鍵字或者ReentrantLock等互斥鎖機制,雖然能夠有效防止資源的競爭沖突,但也可能帶來額外的性能開銷,如上下文切換、鎖競爭導致的線程阻塞等。而此時就出現(xiàn)了一種樂觀鎖的策略,以其非阻塞、輕量級的特點,在某些場合下能更好地提升并發(fā)性能,其中最為關鍵的技術便是Compare And Swap(簡稱CAS)。

CAS是一種無鎖算法,它在硬件級別提供了原子性的條件更新操作,允許線程在不加鎖的情況下實現(xiàn)對共享變量的修改。在Java中,CAS機制被廣泛應用于java.util.concurrent.atomic包下的原子類以及高級并發(fā)工具類如AbstractQueuedSynchronizer(AQS)的實現(xiàn)中。

CAS的基本概念

CAS是原子指令,一種基于鎖的操作,而且是樂觀鎖,又稱無鎖機制。CAS操作包含三個基本操作數(shù):內(nèi)存位置、期望值和新值。

  • 主內(nèi)存中存放的共享變量的值:V(一般情況下這個V是內(nèi)存的地址值,通過這個地址可以獲得內(nèi)存中的值)。
  • 工作內(nèi)存中共享變量的副本值,也叫預期值:A。
  • 需要將共享變量更新到的最新值:B。

CAS基本原理

在執(zhí)行CAS操作時,計算機會檢查內(nèi)存位置當前是否存放著期望值,如果是,則將內(nèi)存位置的值更新為新值;若不是,則不做任何修改,保持原有值不變,并返回當前內(nèi)存位置的實際值。

CAS操作通過一條CPU的原子指令,保證了比較和更新的原子性。在執(zhí)行CAS操作時,CPU會判斷當前系統(tǒng)是否為多核系統(tǒng),如果是,則會給總線加鎖,確保只有一個線程能夠執(zhí)行CAS操作。這種獨占式的原子性實現(xiàn)方式,比起使用synchronized等重量級鎖,具有更短的排他時間,因此在多線程情況下性能更佳。

Java中的CAS實現(xiàn)

在Java中,CAS機制被封裝在jdk.internal.misc.Unsafe類中,盡管這個類并不建議在普通應用程序中直接使用,但它是構(gòu)建更高層次并發(fā)工具的基礎,例如java.util.concurrent.atomic包下的原子類如AtomicInteger、AtomicLong等。這些原子類通過JNI調(diào)用底層硬件提供的CAS指令,從而在Java層面上實現(xiàn)了無鎖并發(fā)操作。

Java的標準庫中,特別是jdk.internal.misc.Unsafe類提供了一系列compareAndSwapXXX方法,這些方法底層確實是通過C++編寫的內(nèi)聯(lián)匯編來調(diào)用對應CPU架構(gòu)的cmpxchg指令,從而實現(xiàn)原子性的比較和交換操作。

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    //由這里可以看出來,依賴jdk.internal.misc.Unsafe實現(xiàn)的
    private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();
    private static final long VALUE = U.objectFieldOffset(AtomicInteger.class, "value");

    private volatile int value;

	public final boolean compareAndSet(int expectedValue, int newValue) { 
	    // 調(diào)用 jdk.internal.misc.Unsafe的compareAndSetInt方法
	    return U.compareAndSetInt(this, VALUE, expectedValue, newValue);  
	}
}

Unsafe中的compareAndSetInt使用了@HotSpotIntrinsicCandidate注解修飾,@HotSpotIntrinsicCandidate注解是Java HotSpot虛擬機(JVM)的一個特性注解,它表明標注的方法有可能會被HotSpot JVM識別為“內(nèi)聯(lián)候選”,當JVM發(fā)現(xiàn)有方法被標記為內(nèi)聯(lián)候選時,會嘗試利用底層硬件提供的原子指令(比如cmpxchg指令)直接替換掉原本的Java方法調(diào)用,從而在運行時獲得更好的性能。

public final class Unsafe {
	@HotSpotIntrinsicCandidate  
	public final native boolean compareAndSetInt(Object o, long offset,  
	                                             int expected,  
	                                             int x);
}                                            
  • compareAndSetInt這個方法我們可以從openjdkhotspot源碼(位置:hotspot/src/share/vm/prims/unsafe.cpp)中可以找到。

  • hostspot中的Unsafe_CompareAndSetInt函數(shù)會統(tǒng)一調(diào)用Atomiccmpxchg函數(shù)

    UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSetInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x)) {
    
    oop p = JNIHandles::resolve(obj);
    
    jint* addr = (jint *)index_oop_from_field_offset_long(p, offset);
    // 統(tǒng)一調(diào)用Atomic的cmpxchg函數(shù)
    return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
    
    } UNSAFE_END
    
  • Atomiccmpxchg函數(shù)源碼(位置:hotspot/src/share/vm/runtime/atomic.hpp)如下:

    /**
    *這是按字節(jié)大小進行的`cmpxchg`操作的默認實現(xiàn)。它使用按整數(shù)大小進行的`cmpxchg`來模擬按字節(jié)大小進行的`cmpxchg`。不同的平臺可以通過定義自己的內(nèi)聯(lián)定義以及定義`VM_HAS_SPECIALIZED_CMPXCHG_BYTE`來覆蓋這個默認實現(xiàn)。這將導致使用特定于平臺的實現(xiàn)而不是默認實現(xiàn)。
    *  exchange_value:要交換的新值。
    *  dest:指向目標字節(jié)的指針。
    *  compare_value:要比較的值。
    *  order:內(nèi)存順序。
    */
    inline jbyte Atomic::cmpxchg(jbyte exchange_value, volatile jbyte* dest,
                                 jbyte compare_value, cmpxchg_memory_order order) {
      STATIC_ASSERT(sizeof(jbyte) == 1);
      volatile jint* dest_int =
          static_cast<volatile jint*>(align_ptr_down(dest, sizeof(jint)));
      size_t offset = pointer_delta(dest, dest_int, 1);
      // 獲取當前整數(shù)大小的值,并將其轉(zhuǎn)換為字節(jié)數(shù)組。
      jint cur = *dest_int;
      jbyte* cur_as_bytes = reinterpret_cast<jbyte*>(&cur);
    
      // 設置當前整數(shù)中對應字節(jié)的值為compare_value。這確保了如果初始的整數(shù)值不是我們要找的值,那么第一次的cmpxchg操作會失敗。
      cur_as_bytes[offset] = compare_value;
    
      // 在循環(huán)中,不斷嘗試更新目標字節(jié)的值。
      do {
        // new_val
        jint new_value = cur;
        // 復制當前整數(shù)值,并設置其中對應字節(jié)的值為exchange_value。
        reinterpret_cast<jbyte*>(&new_value)[offset] = exchange_value;
    	// 嘗試使用新的整數(shù)值替換目標整數(shù)。
        jint res = cmpxchg(new_value, dest_int, cur, order);
        if (res == cur) break; // 如果返回值與原始整數(shù)值相同,說明操作成功。
    
        // 更新當前整數(shù)值為cmpxchg操作的結(jié)果。
        cur = res;
        // 如果目標字節(jié)的值仍然是我們之前設置的值,那么繼續(xù)循環(huán)并再次嘗試。
      } while (cur_as_bytes[offset] == compare_value);
      // 返回更新后的字節(jié)值
      return cur_as_bytes[offset];
    }
    

cmpxchg指令是多數(shù)現(xiàn)代CPU支持的原子指令,它能在多線程環(huán)境下確保一次比較和交換操作的原子性,有效解決了多線程環(huán)境下數(shù)據(jù)競爭的問題,避免了數(shù)據(jù)不一致的情況。例如,在更新一個共享變量時,如果期望值與當前值相匹配,則原子性地更新為新值,否則不進行更新操作,這樣就能在無鎖的情況下實現(xiàn)對共享資源的安全訪問。
我們以java.util.concurrent.atomic包下的AtomicInteger為例,分析其compareAndSet方法。

而由cmpxchg函數(shù)中的do...while我們也可以看出,當多個線程同時嘗試更新同一內(nèi)存位置,且它們的期望值相同但只有一個線程能夠成功更新時,其他線程的CAS操作會失敗。對于失敗的線程,常見的做法是采用自旋鎖的形式,即循環(huán)重試直到成功為止。這種方式在低競爭或短時間窗口內(nèi)的并發(fā)更新時,相比于傳統(tǒng)的鎖機制,它避免了線程的阻塞和喚醒帶來的開銷,所以它的性能會更優(yōu)。

什么是unsafe

什么是unsafe呢?Java語言不像C,C++那樣可以直接訪問底層操作系統(tǒng),但是JVM為我們提供了一個后門,這個后門就是unsafe。unsafe為我們提供了硬件級別的原子操作。

CAS是一種原子操作。那么Java是怎樣來使用CAS的呢?

我們知道,在Java中,如果一個方法是native的,那Java就不負責具體實現(xiàn)它,而是交給底層的JVM使用c或者c++去實現(xiàn)。

Unsafe類是JDK提供的一個不安全的類,它提供了一些底層的操作,包括內(nèi)存操作、線程調(diào)度、對象實例化等。它的作用是讓Java可以在底層直接操作內(nèi)存,從而提高程序的效率。但是,由于Unsafe類是不安全的,所以只有JDK開發(fā)人員才能使用它,普通開發(fā)者不建議使用。它里面大多是一些native方法,其中就有幾個關于CAS的:

boolean compareAndSwapObject(Object o, long offset,Object expected, Object x);
boolean compareAndSwapInt(Object o, long offset,int expected,int x);
boolean compareAndSwapLong(Object o, long offset,long expected,long x);
  • 調(diào)用compareAndSwapInt、compareAndSwapLong或compareAndSwapObject方法時,會傳入三個參數(shù),分別是需要修改的變量V、期望的值A和新值B。

  • 方法會先讀取變量V的當前值,如果當前值等于期望的值A,則使用新值B來更新變量V,否則不做任何操作。

  • 方法會返回更新操作是否成功的標志,如果更新成功,則返回true,否則返回false。

    由于CAS操作是基于底層硬件支持的原子性指令來實現(xiàn)的,所以它可以保證操作的原子性和線程安全性,同時也可以避免使用鎖帶來的性能開銷。因此,CAS操作廣泛應用于并發(fā)編程中,比如實現(xiàn)無鎖數(shù)據(jù)結(jié)構(gòu)、實現(xiàn)線程安全的計數(shù)器等。

原子操作類解析

看一下AtomicInteger當中常用的自增方法incrementAndGet

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;
    /**
     * Atomically increments by one the current value.
     *
     * @return the updated value
     */
    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }
}

public final class Unsafe {
	public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
	public final int getAndAddInt(Object var1, long var2, int var4) {
	        int var5;
	        do {
	            var5 = this.getIntVolatile(var1, var2);
	        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
	
	        return var5;
	}
}

這段代碼是一個無限循環(huán),也就是CAS的自旋(底層為do-while循環(huán)),循環(huán)體中做了三件事:

  • 獲取當前值
  • 當前值 + 1,計算出目標值
  • 進行CAS操作,如果成功則跳出循環(huán),如果失敗則重復上述步驟

這里需要注意的重點是get方法,這個方法的作用是獲取變量的當前值。

volatile關鍵字來保證(保證線程間的可見性)。

compareAndSet方法的實現(xiàn)很簡單,只有一行代碼。這里涉及到兩個重要的對象,一個是unsafe,一個是valueOffset。 unsafe上面提到,就不用多說了,對于valueOffset對象,是通過unsafe.objectFiledOffset方法得到,所代表的是AtomicInteger對象value成員變量在內(nèi)存中的偏移量。我們可以簡單的把valueOffset理解為value變量的內(nèi)存地址。

我們上面說過,CAS機制中使用了3個基本操作數(shù):內(nèi)存地址V,舊的預期值A,要修改的新值B。

而unsafe的compareAndSwapInt方法的參數(shù)包括了這三個基本元素:valueOffset參數(shù)代表了V,expect參數(shù)代表了A,update參數(shù)代表了B。

正是unsafe的compareAndSwapInt方法保證了Compare和Swap操作之間的原子性操作。

CAS機制的優(yōu)缺點

優(yōu)點

一開始在文中我們曾經(jīng)提到過,CAS是一種樂觀鎖,而且是一種非阻塞的輕量級的樂觀鎖,什么是非阻塞式的呢?其實就是一個線程想要獲得鎖,對方會給一個回應表示這個鎖能不能獲得。在資源競爭不激烈的情況下性能高,相比synchronized重量鎖,synchronized會進行比較復雜的加鎖,解鎖和喚醒操作。

缺點

  • CPU開銷過大:CAS摒棄了傳統(tǒng)的鎖機制,避免了因獲取和釋放鎖產(chǎn)生的上下文切換和線程阻塞,從而顯著提升了系統(tǒng)的并發(fā)性能。并且由于CAS操作是基于硬件層面的原子性保證,所以它不會出現(xiàn)死鎖問題,這對于復雜并發(fā)場景下的程序設計特別重要。另外,CAS策略下線程在無法成功更新變量時不需要掛起和喚醒,只需通過簡單的循環(huán)重試即可。

    但是,在高并發(fā)條件下,頻繁的CAS操作可能導致大量的自旋重試,消耗大量的CPU資源。尤其是在競爭激烈的場景中,線程可能花費大量的時間在不斷地嘗試更新變量,而不是做有用的工作。這個由剛才cmpxchg函數(shù)可以看出。對于這個問題,我們可以參考synchronize中輕量級鎖經(jīng)過自旋,超過一定閾值后升級為重量級鎖的原理,我們也可以給自旋設置一個次數(shù),如果超過這個次數(shù),就把線程掛起或者執(zhí)行失敗。(自適應自旋)。

    另外,Java中的原子類也提供了解決辦法,比如LongAdder以及DoubleAdder等,LongAdder過分散競爭點來減少自旋鎖的沖突。它并沒有像AtomicLong那樣維護一個單一的共享變量,而是維護了一個Base值和一組Cell(桶)結(jié)構(gòu)。每個Cell本質(zhì)上也是一個可以進行原子操作的計數(shù)器,多個線程可以分別在一個獨立的Cell上進行累加,只有在必要時才將各個Cell的值匯總到Base中。這樣一來,大部分時候線程間的修改不再是集中在同一個變量上,從而降低了競爭強度,提高了并發(fā)性能。

  • ABA問題: 單純的CAS無法識別一個值被多次修改后又恢復原值的情況,可能導致錯誤的判斷。在高并發(fā)場景下,使用CAS操作可能存在ABA問題,也就是在一個值被修改之前,先被其他線程修改為另外的值,然后再被修改回原值,此時CAS操作會認為這個值沒有被修改過,導致數(shù)據(jù)不一致。

    為了解決ABA問題,Java中提供了AtomicStampedReference類(原子標記參考),該類通過使用版本號的方式來解決ABA問題。每個共享變量都會關聯(lián)一個版本號,CAS操作時需要同時檢查值和版本號是否匹配。因此,如果共享變量的值被改變了,版本號也會發(fā)生變化,即使共享變量被改回原來的值,版本號也不同,因此CAS操作會失敗。

    在java中鎖分為樂觀鎖和悲觀鎖。悲觀鎖是將資源鎖住,等一個之前獲得鎖的線程釋放鎖之后,下一個線程才可以訪問。而樂觀鎖采取了一種寬泛的態(tài)度,通過某種方式不加鎖來處理資源,比如通過給記錄加version來獲取數(shù)據(jù),性能較悲觀鎖有很大的提高。

  • 不能保證代碼塊的原子性: CAS機制所保證的知識一個變量的原子性操作,而不能保證整個代碼塊的原子性。比如需要保證3個變量共同進行原子性的更新,就不得不使用synchronized了。

Java的原子類就提供了類似的實現(xiàn),如AtomicStampedReferenceAtomicMarkableReference引入了附加的標記位或版本號,以便區(qū)分不同的修改序列。

CAS應用場景

主要在并發(fā)編程的應用中非常的廣泛,通常用于實現(xiàn)樂觀鎖和無鎖算法

  • 線程安全計數(shù)器:由于CAS操作是原子性的,因此CAS可以用來實現(xiàn)一個線程安全的計數(shù)器;
  • 隊列: 在并發(fā)編程中,隊列經(jīng)常用于多線程之間的數(shù)據(jù)交換。使用CAS可以實現(xiàn)無鎖的非阻塞隊列(Lock-Free Queue);
  • 數(shù)據(jù)庫并發(fā)控制: 樂觀鎖就是通過CAS實現(xiàn)的,它可以在數(shù)據(jù)庫并發(fā)控制中保證多個事務同時訪問同一數(shù)據(jù)時的一致性;
  • 自旋鎖: 自旋鎖是一種非阻塞鎖,當線程嘗試獲取鎖時,如果鎖已經(jīng)被其他線程占用,則線程不會進入休眠,而是一直在自旋等待鎖的釋放。自旋鎖的實現(xiàn)可以使用CAS操作;
  • 線程池: 在多線程編程中,線程池可以提高線程的使用效率。使用CAS操作可以避免對線程池的加鎖,從而提高線程池的并發(fā)性能。

CAS機制優(yōu)化

雖然CAS機制具有很多優(yōu)點,但在實際應用中也存在一些問題,如自旋等待導致的CPU資源浪費等。為了優(yōu)化CAS機制的性能,可以采取以下措施:

  • 自適應自旋:根據(jù)歷史經(jīng)驗動態(tài)調(diào)整自旋次數(shù),避免過多的自旋等待。
  • 批量操作:將多個CAS操作組合成一個原子塊,減少CAS操作的次數(shù)。
  • 減少鎖競爭:通過合理的數(shù)據(jù)結(jié)構(gòu)設計和線程調(diào)度策略,減少CAS操作的競爭,提高并發(fā)性能。

總結(jié)

Java中的CAS原理及其在并發(fā)編程中的應用是一項非常重要的技術。CAS利用CPU硬件提供的原子指令,實現(xiàn)了在無鎖環(huán)境下的高效并發(fā)控制,避免了傳統(tǒng)鎖機制帶來的上下文切換和線程阻塞開銷。Java通過JNI接口調(diào)用底層的CAS指令,封裝在jdk.internal.misc類和java.util.concurrent.atomic包下的原子類中,為我們提供了簡潔易用的API來實現(xiàn)無鎖編程。

CAS在帶來并發(fā)性能提升的同時,也可能引發(fā)循環(huán)開銷過大、ABA問題等問題。針對這些問題,Java提供了如LongAdder、AtomicStampedReferenceAtomicMarkableReference等工具類來解決ABA問題,同時也通過自適應自旋、適時放棄自旋轉(zhuǎn)而進入阻塞等待等方式降低循環(huán)開銷。

理解和熟練掌握CAS原理及其在Java中的應用,有助于我們在開發(fā)高性能并發(fā)程序時作出更明智的選擇,既能提高系統(tǒng)并發(fā)性能,又能保證數(shù)據(jù)的正確性和一致性。

到此這篇關于Java中CAS機制實現(xiàn)方法的文章就介紹到這了,更多相關Java CAS機制詳解內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • 詳解JavaSE中抽象類與接口的定義及使用

    詳解JavaSE中抽象類與接口的定義及使用

    這篇文章主要為大家詳細介紹了JavaSe的抽象類和接口,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-07-07
  • Java設計模式之命令模式

    Java設計模式之命令模式

    這篇文章介紹了Java設計模式之命令模式,文中通過示例代碼介紹的非常詳細。對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-10-10
  • SpringBoot動態(tài)定時功能實現(xiàn)方案詳解

    SpringBoot動態(tài)定時功能實現(xiàn)方案詳解

    在SpringBoot項目中簡單使用定時任務,不過由于要借助cron表達式且都提前定義好放在配置文件里,不能在項目運行中動態(tài)修改任務執(zhí)行時間,實在不太靈活?,F(xiàn)在我們就來實現(xiàn)可以動態(tài)修改cron表達式的定時任務,感興趣的可以了解一下
    2022-11-11
  • DolphinScheduler容錯Master源碼分析

    DolphinScheduler容錯Master源碼分析

    這篇文章主要為大家介紹了DolphinScheduler容錯Master源碼分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-02-02
  • JVM內(nèi)置函數(shù)Intrinsics介紹

    JVM內(nèi)置函數(shù)Intrinsics介紹

    這篇文章主要介紹了JVM內(nèi)置函數(shù)Intrinsics,我們將學習什么是intrinsics(內(nèi)部/內(nèi)置函數(shù)),以及它們?nèi)绾卧贘ava和其他基于JVM的語言中工作,需要的朋友可以參考一下
    2022-02-02
  • Java實現(xiàn)動態(tài)日歷效果

    Java實現(xiàn)動態(tài)日歷效果

    這篇文章主要為大家詳細介紹了Java實現(xiàn)動態(tài)日歷效果,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-07-07
  • Java中將String類型依照某個字符分割成數(shù)組的方法

    Java中將String類型依照某個字符分割成數(shù)組的方法

    下面小編就為大家分享一篇Java中將String類型依照某個字符分割成數(shù)組的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-03-03
  • Java爬蟲實現(xiàn)Jsoup利用dom方法遍歷Document對象

    Java爬蟲實現(xiàn)Jsoup利用dom方法遍歷Document對象

    本文主要介紹了Java爬蟲實現(xiàn)Jsoup利用dom方法遍歷Document對象,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2022-05-05
  • 詳解springboot測試類注解

    詳解springboot測試類注解

    這篇文章主要介紹了springboot測試類注解,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-07-07
  • 如何將Set直接轉(zhuǎn)成數(shù)組

    如何將Set直接轉(zhuǎn)成數(shù)組

    這篇文章主要介紹了如何將Set直接轉(zhuǎn)成數(shù)組,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-10-10

最新評論