亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Java?CAS機(jī)制詳解

 更新時(shí)間:2023年01月18日 16:12:27   作者:氵奄不死的魚  
這篇文章主要介紹了Java?CAS機(jī)制,CAS機(jī)制是一種數(shù)據(jù)更新的方式。在具體講什么是CAS機(jī)制之前,我們先來聊下在多線程環(huán)境下,對共享變量進(jìn)行數(shù)據(jù)更新的兩種模式:悲觀鎖模式和樂觀鎖模式

一、什么是CAS

什么是CAS機(jī)制

CAS機(jī)制是一種數(shù)據(jù)更新的方式。在具體講什么是CAS機(jī)制之前,我們先來聊下在多線程環(huán)境下,對共享變量進(jìn)行數(shù)據(jù)更新的兩種模式:悲觀鎖模式和樂觀鎖模式。

悲觀鎖更新的方式認(rèn)為:在更新數(shù)據(jù)的時(shí)候大概率會有其他線程去爭奪共享資源,所以悲觀鎖的做法是:第一個(gè)獲取資源的線程會將資源鎖定起來,其他沒爭奪到資源的線程只能進(jìn)入阻塞隊(duì)列,等第一個(gè)獲取資源的線程釋放鎖之后,這些線程才能有機(jī)會重新爭奪資源。synchronized就是java中悲觀鎖的典型實(shí)現(xiàn),synchronized使用起來非常簡單方便,但是會使沒爭搶到資源的線程進(jìn)入阻塞狀態(tài)**,線程在阻塞狀態(tài)和Runnable狀態(tài)之間切換效率較低(比較慢)。比如你的更新操作其實(shí)是非??斓模@種情況下你還用synchronized將其他線程都鎖住了,線程從Blocked狀態(tài)切換回Runnable華的時(shí)間可能比你的更新操作的時(shí)間還要長。**

樂觀鎖更新方式認(rèn)為:在更新數(shù)據(jù)的時(shí)候其他線程爭搶這個(gè)共享變量的概率非常小,所以更新數(shù)據(jù)的時(shí)候不會對共享數(shù)據(jù)加鎖。但是在正式更新數(shù)據(jù)之前會檢查數(shù)據(jù)是否被其他線程改變過,如果未被其他線程改變過就將共享變量更新成最新值,如果發(fā)現(xiàn)共享變量已經(jīng)被其他線程更新過了,就重試,直到成功為止。CAS機(jī)制就是樂觀鎖的典型實(shí)現(xiàn)。

CAS,是Compare and Swap的簡稱,在這個(gè)機(jī)制中有三個(gè)核心的參數(shù):

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

CAS,compare and swap的縮寫,中文翻譯成比較并交換。

CAS 操作包含三個(gè)操作數(shù) —— 內(nèi)存位置(V)、預(yù)期原值(A)和新值(B)。 如果內(nèi)存位置的值與預(yù)期原值相匹配,那么處理器會自動將該位置值更新為新值 。否則,處理器不做任何操作。

為何CAS如此優(yōu)秀

硬件加持,現(xiàn)代大多數(shù)處理器都從硬件層面通過一些列指令實(shí)現(xiàn)CompareAndSwap(比較并交換)同步原語,進(jìn)而使操作系統(tǒng)和JVM可以直接使用這些指令實(shí)現(xiàn)鎖和并發(fā)的數(shù)據(jù)結(jié)構(gòu)。我們可以簡單認(rèn)為,CAS是將比較和交換合成是一個(gè)原子操作。

JVM對CAS的支持, 由于Java程序運(yùn)行在JVM上,所以應(yīng)對不同的硬件體系架構(gòu)的處理則需要JVM來實(shí)現(xiàn)。在不支持CAS操作的硬件上,jvm將使用自旋鎖來實(shí)現(xiàn)。

CAS為什么要和volitile配合使用

cas保證原子性。volitile保證可見性和有序性,二者加起來,保證線程安全!

二、Java中的Atomic原子操作包

JUC 并發(fā)包中原子類 , 都存放在 java.util.concurrent.atomic 類路徑下:

根據(jù)操作的目標(biāo)數(shù)據(jù)類型,可以將 JUC 包中的原子類分為 4 類:

基本原子類

數(shù)組原子類

原子引用類型

字段更新原子類

1. 基本原子類

基本原子類的功能,是通過原子方式更新 Java 基礎(chǔ)類型變量的值?;驹宇愔饕艘韵氯齻€(gè):

  • AtomicInteger:整型原子類。
  • AtomicLong:長整型原子類。
  • AtomicBoolean :布爾型原子類。

2. 數(shù)組原子類

數(shù)組原子類的功能,是通過原子方式更數(shù)組里的某個(gè)元素的值。數(shù)組原子類主要包括了以下三個(gè):

  • AtomicIntegerArray:整型數(shù)組原子類。
  • AtomicLongArray:長整型數(shù)組原子類。
  • AtomicReferenceArray :引用類型數(shù)組原子類。

3. 引用原子類(可以獎多個(gè)數(shù)據(jù)聚合成一個(gè)對象坐cas操作,但是不要直接修改原對象,而是每次復(fù)制處新對象,在新對象生更改后,通過cas設(shè)置回去)。

引用原子類主要包括了以下三個(gè):

  • AtomicReference:引用類型原子類
  • AtomicMarkableReference :帶有更新標(biāo)記位的原子引用類型。
  • AtomicStampedReference :帶有更新版本號的原子引用類型。

AtomicStampedReference通過引入“版本”的概念,來解決ABA的問題。

4. 字段更新原子類

字段更新原子類主要包括了以下三個(gè):

  • AtomicIntegerFieldUpdater:原子更新整型字段的更新器。
  • AtomicLongFieldUpdater:原子更新長整型字段的更新器。
  • AtomicReferenceFieldUpdater:原子更新引用類型里的字段。

三、類AtomicInteger

1、常用的方法:

方法 介紹

public final int get() 獲取當(dāng)前的值

public final int getAndSet(int newValue) 獲取當(dāng)前的值,然后設(shè)置新的值

public final int getAndIncrement() 獲取當(dāng)前的值,然后自增

public final int getAndDecrement() 獲取當(dāng)前的值,然后自減

public final int getAndAdd(int delta) 獲取當(dāng)前的值,并加上預(yù)期的值

boolean compareAndSet(int expect, int update) 通過 CAS 方式設(shè)置整數(shù)值

AtomicInteger 案例:

 private static void out(int oldValue,int newValue){
        System.out.println("舊值:"+oldValue+",新值:"+newValue);
    }
    public static void main(String[] args) {
        int value = 0;
        AtomicInteger atomicInteger= new AtomicInteger(0);
        //取值,然后設(shè)置一個(gè)新值
        value = atomicInteger.getAndSet(3);
        //舊值:0,新值:3
        out(value,atomicInteger.get());
        //取值,然后自增
        value = atomicInteger.getAndIncrement();
        //舊值:3,新值:4
        out(value,atomicInteger.get());
        //取值,然后增加 5
        value = atomicInteger.getAndAdd(5);
        //舊值:4,新值:9
        out(value,atomicInteger.get());
        //CAS 交換
        boolean flag = atomicInteger.compareAndSet(9, 100);
        //舊值:4,新值:100
        out(value,atomicInteger.get());
    }

2、AtomicInteger 源碼解析:

public class AtomicInteger extends Number implements java.io.Serializable {
    // 設(shè)置使用Unsafe.compareAndSwapInt進(jìn)行更新
    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;
    //自動設(shè)置為給定值并返回舊值。
    public final int getAndSet(int newValue) {
        return unsafe.getAndSetInt(this, valueOffset, newValue);
    }
    //以原子方式將當(dāng)前值加1并返回舊值。
    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }
    //以原子方式將當(dāng)前值減1并返回舊值。
    public final int getAndDecrement() {
        return unsafe.getAndAddInt(this, valueOffset, -1);
    }
    //原子地將給定值添加到當(dāng)前值并返回舊值。
    public final int getAndAdd(int delta) {
        return unsafe.getAndAddInt(this, valueOffset, delta);
    }
    ...省略
}

通過源碼我們發(fā)現(xiàn)AtomicInteger的增減操作都調(diào)用了Unsafe 實(shí)例的方法,下面我們對Unsafe類做介紹:

四、Unsafe類

Unsafe 是位于 sun.misc 包下的一個(gè)類,Unsafe 提供了CAS 方法,直接通過native 方式(封裝 C++代碼)調(diào)用了底層的 CPU 指令 cmpxchg。

Unsafe類,翻譯為中文:危險(xiǎn)的,Unsafe全限定名是 sun.misc.Unsafe,從名字中我們可以看出來這個(gè)類對普通程序員來說是“危險(xiǎn)”的,一般應(yīng)用開發(fā)者不會用到這個(gè)類。

1、Unsafe 提供的 CAS 方法

主要如下: 定義在 Unsafe 類中的三個(gè) “比較并交換”原子方法

/*
@param o 包含要修改的字段的對象
@param offset 字段在對象內(nèi)的偏移量
@param expected 期望值(舊的值)
@param update 更新值(新的值)
@return true 更新成功 | false 更新失敗
*/
public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object update);
public final native boolean compareAndSwapInt( Object o, long offset, int expected,int update);
public final native boolean compareAndSwapLong( Object o, long offset, long expected, long update);

Unsafe 提供的 CAS 方法包含四個(gè)入?yún)ⅲ?包含要修改的字段對象、字段內(nèi)存位置、預(yù)期原值及

新值。在執(zhí)行 Unsafe 的 CAS 方法的時(shí)候,這些方法首先將內(nèi)存位置的值與預(yù)期值(舊的值)比

較,如果相匹配,那么處理器會自動將該內(nèi)存位置的值更新為新值,并返回 true ;如果不相匹配,

處理器不做任何操作,并返回 false 。

2、獲取屬性偏移量

Unsafe 提供的獲取字段(屬性)偏移量的相關(guān)操作,主要如下:

/**
* @param o 需要操作屬性的反射 
* @return 屬性的偏移量 
*/ 
public native long staticFieldOffset(Field field); 
public native long objectFieldOffset(Field field);

staticFieldOffset 方法用于獲取靜態(tài)屬性 Field 在 Class 對象中的偏移量,在 CAS 操作靜態(tài)屬性時(shí),會用到這個(gè)偏移量。

objectFieldOffset 方法用于獲取非靜態(tài) Field (非靜態(tài)屬性)在 Object 實(shí)例中的偏移量,在 CAS 操作對象的非靜態(tài)屬性時(shí),會用到這個(gè)偏移量。

3、根據(jù)屬性的偏移量獲取屬性的最新值:

/**
* @param o 字段所屬于的對象實(shí)例
* @param fieldOffset 字段的偏移量 
* @return 字段的最新值
*/
public native int getIntVolatile(Object o, long fieldOffset);

五、CAS的缺點(diǎn)

ABA問題。因?yàn)镃AS需要在操作值的時(shí)候檢查下值有沒有發(fā)生變化,如果沒有發(fā)生變化則更新,但是如果一個(gè)值原來是A,變成了B,又變成了A,那么使用CAS進(jìn)行檢查時(shí)會發(fā)現(xiàn)它的值沒有發(fā)生變化,但是實(shí)際上卻變化了。

JDK 提供了兩個(gè)類 AtomicStampedReference、AtomicMarkableReference 來解決 ABA 問題。

只能保證一個(gè)共享變量的原子操作。一個(gè)比較簡單的規(guī)避方法為:把多個(gè)共享變量合并成一個(gè)共享變量來操作。 JDK 提供了 AtomicReference 類來保證引用對象之間的原子性,可以把多個(gè)變量放在一個(gè) AtomicReference 實(shí)例后再進(jìn)行 CAS 操作。比如有兩個(gè)共享變量 i=1、j=2,可以將二者合并成一個(gè)對象,然后用 CAS 來操作該合并對象的 AtomicReference 引用。

循環(huán)時(shí)間長開銷大。高并發(fā)下N多線程同時(shí)去操作一個(gè)變量,會造成大量線程CAS失敗,然后處于自旋狀態(tài),導(dǎo)致嚴(yán)重浪費(fèi)CPU資源,降低了并發(fā)性。

解決 CAS 惡性空自旋的較為常見的方案為:

分散操作熱點(diǎn),使用 LongAdder 替代基礎(chǔ)原子類 AtomicLong。

使用隊(duì)列削峰,將發(fā)生 CAS 爭用的線程加入一個(gè)隊(duì)列中排隊(duì),降低 CAS 爭用的激烈程度。JUC 中非常重要的基礎(chǔ)類 AQS(抽象隊(duì)列同步器)就是這么做的。

六、以空間換時(shí)間LongAdder 

1、LongAdder 的原理

LongAdder 的基本思路就是分散熱點(diǎn), 如果有競爭的話,內(nèi)部維護(hù)了多個(gè)Cell變量,每個(gè)Cell里面有一個(gè)初始值為0的long型變量, 不同線程會命中到數(shù)組的不同Cell (槽 )中,各個(gè)線程只對自己Cell(槽) 中的那個(gè)值進(jìn)行 CAS 操作。這樣熱點(diǎn)就被分散了,沖突的概率就小很多。 在沒有競爭的情況下,要累加的數(shù)通過 CAS 累加到 base 上。 如果要獲得完整的 LongAdder 存儲的值,只要將各個(gè)槽中的變量值累加,后的值即可。

七、使用AtomicStampedReference解決ABA問題

JDK 的提供了一個(gè)類似 AtomicStampedReference 類來解決 ABA 問題。

AtomicStampReference 在 CAS 的基礎(chǔ)上增加了一個(gè) Stamp 整型 印戳(或標(biāo)記),使用這個(gè)印戳可以來覺察數(shù)據(jù)是否發(fā)生變化,給數(shù)據(jù)帶上了一種實(shí)效性的檢驗(yàn)。

AtomicStampReference 的 compareAndSet 方法首先檢查當(dāng)前的對象引用值是否等于預(yù)期引用, 并且當(dāng)前印戳( Stamp )標(biāo)志是否等于預(yù)期標(biāo)志,如果全部相等,則以原子方式將引用值和印戳 ( Stamp )標(biāo)志的值更新為給定的更新值。 1、AtomicStampReference 的構(gòu)造器:

/**  
* @param initialRef初始引用  
* @param initialStamp初始戳記  
*\ 
AtomicStampedReference(V initialRef, int initialStamp)

2、AtomicStampReference 的常用的幾個(gè)方法如下:

方法 介紹

public V getRerference() 引用的當(dāng)前值 public int getStamp() 返回當(dāng)前的"戳記"

public boolean weakCompareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp)
expectedReference 引用的舊值
newReference 引用的新值
expectedStamp 舊的戳記
newStamp 新的戳記  
    public static void main(String[] args) {
        boolean success = false;
        AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<Integer>(1, 0);
        int stamp = atomicStampedReference.getStamp();
        success = atomicStampedReference.compareAndSet(1, 0, stamp, stamp + 1);
        System.out.println("success:" + success + ";reference:" + "" + atomicStampedReference.getReference() + ";stamp:" + atomicStampedReference.getStamp());
        //修改印戳,更新失敗
        stamp = 0;
        success = atomicStampedReference.compareAndSet(0, 1, stamp, stamp + 1);
        System.out.println("success:" + success + ";reference:" + "" + atomicStampedReference.getReference() + ";stamp:" + atomicStampedReference.getStamp());
    }

八、AtomicIntegerFieldUpdater進(jìn)行字段更新

如果一個(gè)類是自己編寫的,則可以在編寫的時(shí)候把成員變量定義為Atomic類型。但如果是一個(gè)已經(jīng)有的類,在不能更改其源代碼的情況下,要想實(shí)現(xiàn)對其成員變量的原子操作,就需要AtomicIntegerFieldUpdater、AtomicLongFieldUpdater 和 AtomicReferenceFieldUpdater。

    public static void main(String[] args) {
        Student student = new Student();
        //創(chuàng)建AtomicIntegerFieldUpdater對象
        AtomicIntegerFieldUpdater<Student> studentAtomicIntegerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(Student.class, "age");
        //打印age并將age+1
        System.out.println(studentAtomicIntegerFieldUpdater.getAndIncrement(student));
        System.out.println(student.age);
    }
//測試類   
public class Student {
	//因?yàn)槭怯梅瓷鋵?shí)現(xiàn)的這里必須要使用public修飾
    public volatile int  age;
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

要修改的age必須是int不能是包裝類Integer,必須被volatile修飾

if (field.getType() != int.class)
throw new IllegalArgumentException("Must be integer type");
if ( ! Modifier.isVolatile(modifiers))
throw new IllegalArgumentException( "Must be volation type");

AtomicIntegerFieldUpdater代碼實(shí)現(xiàn)

	//AtomicIntegerFieldUpdater的無參構(gòu)造被protected修飾,使用newUpdater創(chuàng)建對象
    protected AtomicIntegerFieldUpdater() {
    }
    public static <U> AtomicIntegerFieldUpdater<U> newUpdater(Class<U> tclass,
                                                              String fieldName) {
        return new AtomicIntegerFieldUpdaterImpl<U>
            (tclass, fieldName, Reflection.getCallerClass());
    }
        public final int getAndIncrement(T obj) {
        	//age+1
            return getAndAdd(obj, 1);
        }
        public final int getAndAdd(T obj, int delta) {
        	//檢查obj和聲明AtomicIntegerFieldUpdater時(shí)的class一不一樣
            accessCheck(obj);
            //Unsafe類獲得age的值并且使用compareAndSwapInt將age+1
            return U.getAndAddInt(obj, offset, delta);
        }

AtomicLongFieldUpdater 和 AtomicReferenceFieldUpdater和AtomicIntegerFieldUpdater類似

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

相關(guān)文章

  • Java?AQS?線程安全同步隊(duì)列的實(shí)現(xiàn)

    Java?AQS?線程安全同步隊(duì)列的實(shí)現(xiàn)

    AQS 同步隊(duì)列是很多的 Java 線程安全對象的實(shí)現(xiàn),例如 ReentrantLock, Semaphore, CountDownLatch, ReentrantReadWriteLock 等等,本文就介紹了Java?AQS?線程安全同步隊(duì)列的實(shí)現(xiàn),感興趣的可以了解一下
    2023-08-08
  • Java詳細(xì)講解堆排序與時(shí)間復(fù)雜度的概念

    Java詳細(xì)講解堆排序與時(shí)間復(fù)雜度的概念

    本文主要介紹了java實(shí)現(xiàn)堆排序以及時(shí)間復(fù)雜度,堆排序這種排序算法是我們經(jīng)常用到的,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • Java中結(jié)束循環(huán)的方法

    Java中結(jié)束循環(huán)的方法

    這篇文章主要介紹了Java中結(jié)束循環(huán)的方法,文中有段代碼在return,結(jié)束了整個(gè)main方法,即使輸出hello world的語句位于循環(huán)體外,也不會被執(zhí)行,對java結(jié)束循環(huán)方法感興趣的朋友跟隨小編一起看看吧
    2023-06-06
  • 使用Java如何將圖片轉(zhuǎn)成Base64編碼,并壓縮至40k

    使用Java如何將圖片轉(zhuǎn)成Base64編碼,并壓縮至40k

    這篇文章主要介紹了使用Java如何將圖片轉(zhuǎn)成Base64編碼,并壓縮至40k問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-06-06
  • JAVA 對接騰訊云直播的實(shí)現(xiàn)

    JAVA 對接騰訊云直播的實(shí)現(xiàn)

    這篇文章主要介紹了JAVA 對接騰訊云直播的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-06-06
  • java中使用map排序的實(shí)例講解

    java中使用map排序的實(shí)例講解

    在本篇文章里小編給大家整理了一篇關(guān)于java中使用map排序的實(shí)例講解內(nèi)容,有興趣的朋友們可以學(xué)習(xí)下。
    2020-12-12
  • Java 實(shí)現(xiàn)麥克風(fēng)自動錄音

    Java 實(shí)現(xiàn)麥克風(fēng)自動錄音

    這篇文章主要介紹了Java 實(shí)現(xiàn)麥克風(fēng)自動錄音的示例代碼,幫助大家更好的理解和使用Java,感興趣的朋友可以了解下
    2020-12-12
  • SWT JFace 小制作 文本閱讀器

    SWT JFace 小制作 文本閱讀器

    SWT JFace 小制作 文本閱讀器
    2009-06-06
  • 淺談Java中Properties類的詳細(xì)使用

    淺談Java中Properties類的詳細(xì)使用

    properties類繼承自hashtable,通常和io流結(jié)合使用。它最突出的特點(diǎn)是將key/value作為配置屬性寫入到配置文件中以實(shí)現(xiàn)配置持久化,或從配置文件中讀取這些屬性。它的這些配置文件的規(guī)范后綴名為".properties"。表示了一個(gè)持久的屬性集
    2021-06-06
  • 詳解使用Spring AOP和自定義注解進(jìn)行參數(shù)檢查

    詳解使用Spring AOP和自定義注解進(jìn)行參數(shù)檢查

    本篇文章主要介紹了詳解使用Spring AOP和自定義注解進(jìn)行參數(shù)檢查,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-04-04

最新評論