最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java多線程之CAS機制詳解

 更新時間:2023年07月20日 11:07:42   作者:一只愛打拳的程序猿  
這篇文章主要介紹了Java多線程之CAS機制詳解,CAS指的是Compare-And-Swap(比較與交換),它是一種多線程同步的技術(shù),常用于實現(xiàn)無鎖算法,從而提高多線程程序的性能和擴展性,需要的朋友可以參考下

1. 什么是CAS?

CAS 全名 compare and swap (比較并交換)是一種基于 Java 實現(xiàn)的 計算機代數(shù)系統(tǒng),用于多線程并發(fā)編程時數(shù)據(jù)在無鎖的情況下保證線程安全安全運行。

CAS機制 主要用于對一個變量(操作)進行原子性的操作,它包含三個參數(shù)值:需要進行操作的變量A、變量的舊值B、即將要更改的新值C。

CAS機制 會對當(dāng)前內(nèi)存中的 A 進行判斷看是否等同于 B ,如果相等則把 A 值更改為 C ,否則不進行操作。以下為 CAS 操作的一段偽代碼:

        boolean CAS(A,B,C) {
            if (&A == B) {
                &A = C;
                return true;
            }
            return false;
        }

當(dāng)然,以上代碼不具有原子性只是簡單理解 CAS 的判定以及返回機制。真正的 CAS 只是一條 CPU 指令,相比于上述代碼具有原子性 。

在了解 CAS 的基本判定后下面我們來看如何通過 Java 標(biāo)準庫來運用 CAS 。

2. CAS的應(yīng)用

2.1 實現(xiàn)原子類

CAS 可以不加鎖保證操作的原子性,Java 標(biāo)準庫提供了 Atomic + 包裝類,相關(guān)的組合類來實現(xiàn)原子操作,這些類都是在 java.util.concurrent.atomic 包底下的。

以常用的 AtomicInteger 類來舉例,AtomicInteger 類底下的 getAndIncrement 方法達到的效果就是自增類似于 i++ 操作,getAndDecrement 方法就是自減類似于 i-- 操作。

因此 AtomicInteger 類常見的方法有:

  • getAndIncrement 方法,自增操作,類似于 i++。
  • getAndDecrement 方法,自減操作,類似于 i--。
  • get 方法,獲取當(dāng)前 AtomicInteger 類引用的值。

當(dāng)然,Atomic + 其他“數(shù)值”包裝類也能使用以上方法!

代碼案例,不使用 synchronized 的情況下保證一個線程自增5000,另一個線程也自增5000,最后返回兩線程之和10000:

public static void main(String[] args) throws InterruptedException {
        //初始化number為0
        AtomicInteger number = new AtomicInteger(0);
        //線程1使number自增5000次
        Thread thread1 = new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                number.getAndIncrement();
            }
        });
        //線程2也使number自增5000次(在線程1執(zhí)行后)
        Thread thread2 = new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                number.getAndIncrement();
            }
        });
        thread1.start();//啟動線程1
        thread2.start();//啟動線程2
        thread1.join();//等待線程1執(zhí)行完畢
        thread2.join();//等下線程2執(zhí)行完畢
        System.out.println(number.get());//輸出number的值
    }

 運行后打?。?/p>

以上代碼,在不使用鎖(synchronized)的情況下保證了線程的安全性。其底層運用的就是 CAS 機制,getAndIncrement 方法的具體實現(xiàn),我們可以參考以下 偽代碼 來理解:

class MyAtomicInteger {
    private int value;
    public int getAndIncrement() {
        int oldValue = value;
        while (CAS(value,oldValue,oldValue + 1) != true) {
            oldValue = value;
        }
        return oldValue;
    }
}

假設(shè) getAndIncrement 方法被兩個線程同時調(diào)用,線程1 和 線程2 的 oldValue 值都為 0,內(nèi)存中的 value 值為0。

1)線程1 進入了 getAndIncrement 方法,此時線程1進行 CAS 判定,發(fā)現(xiàn)線程1的 oldValue = value,就把 value 進行自增。

2) 線程2 進入了 getAndIncrement 方法,此時 線程2 進行 CAS 判定,發(fā)現(xiàn) oldValue != value,進入 while 循環(huán),把 value 賦值給 old Value。

3)經(jīng)過以上判斷后,線程2 再次進行 CAS 判斷時,發(fā)現(xiàn) oldValue = value 了,此時的 value 值又會自增。

以上的 偽代碼 就能實現(xiàn)一個原子類,里面的 getAndIncrement 方法也是具備原子性的。通過上述圖例就能很好的理解。

2.2 實現(xiàn)自旋鎖

CAS的自旋鎖指的是在使用CAS操作時,當(dāng)CAS操作失敗后,線程不直接阻塞等待,而是繼續(xù)嘗試執(zhí)行CAS操作,即對前一次CAS操作的失敗進行重試,直到CAS操作成功為止。

自旋鎖的意思是程序使用循環(huán)來等待特定條件的實現(xiàn)方式,相較于傳統(tǒng)的阻塞鎖,自旋鎖不會使線程進入阻塞狀態(tài),因此避免了線程上下文切換帶來的開銷。通常,當(dāng)線程競爭的資源空閑等待的時間不長,自旋鎖是一種比較高效的同步機制。

CAS 自旋鎖體現(xiàn):一段 偽代碼 :

public class SpinLock {
  private Thread owner = null;
  public void lock(){
    // 通過 CAS 看當(dāng)前鎖是否被某個線程持有.
    // 如果這個鎖已經(jīng)被別的線程持有, 那么就自旋等待.
    // 如果這個鎖沒有被別的線程持有, 那么就把 owner 設(shè)為當(dāng)前嘗試加鎖的線程.
    while(!CAS(this.owner, null, Thread.currentThread())){
   }
 }
  public void unlock (){
    this.owner = null;
 }
}

Thread.currentThread() 為當(dāng)前對象的引用,以上代碼進行 CAS 判定時:

如果判斷 this.owner 為空,則把當(dāng)前對象的引用賦值給 this.owner。此時 CAS 方法返回 true,并取反,while 循環(huán)退出。判斷 this.owner 不為空,則不做任何操作,CAS 方法返回 false,并取反,while 循環(huán)繼續(xù)執(zhí)行。由于 while 循環(huán)體內(nèi)沒有任何內(nèi)容,while 條件判斷會執(zhí)行很快,直到 this.owner 加鎖成功為止。

這就是自旋鎖的體現(xiàn),關(guān)于鎖的策略在本專欄中有詳細講解。大家可以前去查找。

3. CAS的ABA問題

ABA 問題是:當(dāng)線程1首先讀取到共享變量值A(chǔ)。然后線程2先把這個共享變量值修改為B,再修改回A。

此時其他線程再進行 CAS 操作時誤以為共享變量值沒有被修改過,從而成功的將共享變量更改為新值。

但實際過程中共享變量經(jīng)歷了 由 A 變?yōu)?B,再由 B 變?yōu)?A,這樣就可能會導(dǎo)致一些問題。

類似于,網(wǎng)上購買一部二手機。買的時候,賣家說是零件完好,到手后才發(fā)現(xiàn)是一部翻新機。這樣就會導(dǎo)致手機用不了幾天就出問題。至于到手之前,賣家不說是識別不出這部手機的好壞的。

3.1 ABA問題可能引起的BUG

ABA 問題,就是 CAS 機制導(dǎo)致的數(shù)據(jù)反復(fù)橫跳。

假設(shè),張三要去 ATM 取錢,張三余額有 1000 元,他要取 500 元。他安排兩個線程,線程1 和 線程2 來并發(fā)執(zhí)行取錢操作。

預(yù)期效果:線程1 執(zhí)行取錢操作判斷余額為 1000,執(zhí)行余額 -500 操作,此時余額 500,線程2 處于阻塞等待狀態(tài)。當(dāng) 線程2 執(zhí)行取錢操作判斷余額不是 1000 不執(zhí)行 -500 操作。

ABA問題出現(xiàn):線程 1 執(zhí)行取錢操作判斷余額為 1000,執(zhí)行余額 -500 操作,此時余額 500,線程2 阻塞等待狀態(tài)。突然,張三的朋友給他轉(zhuǎn)賬了 500 ,此時 余額又變回了 1000。

線程2 進入取錢操作時,判斷余額為 1000 元,執(zhí)行余額 -500 操作,此時余額剩余 500。這就是 ABA 問題造成的后果,張三回家后打開手機查看余額剩余 500,實際張三被 ABA 問題坑了 500元。

3.2 解決ABA問題

CAS 操作,是將需要改變的值 A 與舊值 B 進行比較,相等則把新值 C 賦值給 A ,否則不做改變。解決 CAS 出現(xiàn) ABA 問題,我們可以引入一個版本號,比較版本號是否符合預(yù)期。

比如在網(wǎng)上購買一部二手機,賣家會將手機的翻新程度進行一個版本號標(biāo)記,翻新1次記版本號1,翻新2次的記版本號2,以此類推。這時候,客戶會根據(jù)版本號來選擇翻新程度相應(yīng)的手機。

  • 當(dāng)版本號和讀到的版本號相等,則修改數(shù)據(jù),并把版本號 + 1。
  • 當(dāng)版本號高于讀到的版本號,就操作失敗(認為數(shù)據(jù)已經(jīng)被修改過了)

根據(jù)以下 偽代碼 來理解:

num = 0;
version = 1;
old = version;
CAS(version,old,old+1,num);
public void CAS(version,oldVersion,oldVersion+1,num){
    if(version == oldVersion) {
        version = oldVersion + 1;
        num++;
    }
}

對以上代碼進行一個講解, version 作為版本號,當(dāng) version 版本號等于讀到的 oldVersion 版本號,則把 oldVersion +1 賦值給 version,并且 num ++ 。這樣就能避免 ABA 問題的出現(xiàn)。

當(dāng)然,Java 中 提供了一個 AtomicStampedReference<>類,這個類可以對某個類進行保證,這樣就能提供上述的版本號管理功能。

public class TestDemo {
    private static final AtomicStampedReference<Integer> sharedValue = new AtomicStampedReference<>(10, 0);
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            int expectedStamp = sharedValue.getStamp();
            int newValue = 20;
            sharedValue.compareAndSet(10, newValue, expectedStamp, expectedStamp + 1);
            System.out.println(Thread.currentThread().getName() + " updated sharedValue to " + newValue);
        }, "Thread-1");
        Thread thread2 = new Thread(() -> {
            int expectedStamp = sharedValue.getStamp();
            int oldValue = sharedValue.getReference();
            int newValue = 30;
            sharedValue.compareAndSet(oldValue, newValue, expectedStamp, expectedStamp + 1);
            System.out.println(Thread.currentThread().getName() + " updated sharedValue to " + newValue);
        }, "Thread-2");
        thread1.start();
        thread1.join();
        thread2.start();
        thread2.join();
        System.out.println("final value: " + sharedValue.getReference());
    }
}

運行后打?。?/p>

以上代碼,共享變量的初始值為10,然后線程1將共享變量的值修改為20,線程2將共享變量的值修改為30。由于AtomicStampedReference類包含版本號信息,因此即使共享變量的值在這個過程中發(fā)生了ABA的變化,CAS操作也可以正常進行,不會出現(xiàn)誤判現(xiàn)象。

談?wù)勀銓?CAS 機制的理解?

CAS 全稱 compare and swap 即比較并交換,它通過一個原子的操作完成“讀取內(nèi)存,比較是否相等,修改內(nèi)存”這三個步驟,本質(zhì)上需要 CPU 指令的支持。

ABA 問題如何解決?

我們可以給修改的數(shù)據(jù)加上一個版本號,初始化當(dāng)前版本號與舊的版本號相等。判斷當(dāng)前版本號如果等于舊版本號則對數(shù)據(jù)進行修改,并使版本號自增。判斷當(dāng)前版本號大于舊版本號,則不進行任何操作。

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

相關(guān)文章

  • 使用Java計算集合中的組內(nèi)平均值的代碼實現(xiàn)

    使用Java計算集合中的組內(nèi)平均值的代碼實現(xiàn)

    在Java開發(fā)中,集合(Collection)是一個重要的數(shù)據(jù)結(jié)構(gòu),廣泛應(yīng)用于各種場景,計算集合中的組內(nèi)平均值是一個常見的操作,本文將深入探討如何使用Java來計算集合中的組內(nèi)平均值,涵蓋基本概念、具體實現(xiàn)、優(yōu)化策略和實用示例,需要的朋友可以參考下
    2024-06-06
  • Java調(diào)用騰訊云短信API接口的實現(xiàn)

    Java調(diào)用騰訊云短信API接口的實現(xiàn)

    這篇文章主要介紹了Java調(diào)用騰訊云短信API接口的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • Springboot vue導(dǎo)出功能實現(xiàn)代碼

    Springboot vue導(dǎo)出功能實現(xiàn)代碼

    這篇文章主要介紹了Springboot vue導(dǎo)出功能實現(xiàn)代碼,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-04-04
  • 從log4j2到Disruptor詳解

    從log4j2到Disruptor詳解

    這篇文章主要介紹了從log4j2到Disruptor詳解,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • 在SpringBoot項目中獲取Request的四種方法

    在SpringBoot項目中獲取Request的四種方法

    這篇文章主要為大家詳細介紹了SpringBoot項目中獲取Request的四種方法,文中的示例代碼講解詳細,具有一定的參考價值,感興趣的小伙伴可以學(xué)習(xí)一下
    2023-11-11
  • dubbo如何實現(xiàn)consumer從多個group中調(diào)用指定group的provider

    dubbo如何實現(xiàn)consumer從多個group中調(diào)用指定group的provider

    這篇文章主要介紹了dubbo如何實現(xiàn)consumer從多個group中調(diào)用指定group的provider問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-03-03
  • 自定義mybatis插件如何實現(xiàn)sql日志打印

    自定義mybatis插件如何實現(xiàn)sql日志打印

    這篇文章主要介紹了自定義mybatis插件如何實現(xiàn)sql日志打印問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-05-05
  • JSON.toJSONString()方法在Java中的使用方法及應(yīng)用場景

    JSON.toJSONString()方法在Java中的使用方法及應(yīng)用場景

    這篇文章主要給大家介紹了關(guān)于JSON.toJSONString()方法在Java中的使用方法及應(yīng)用場景,JSON.toJSONString是將對象轉(zhuǎn)化為Json字符串,文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2024-04-04
  • Springboot+Shiro記錄用戶登錄信息并獲取當(dāng)前登錄用戶信息的實現(xiàn)代碼

    Springboot+Shiro記錄用戶登錄信息并獲取當(dāng)前登錄用戶信息的實現(xiàn)代碼

    這篇文章主要介紹了Springboot+Shiro記錄用戶登錄信息,并獲取當(dāng)前登錄用戶信息,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-05-05
  • 關(guān)于ReadWriteLock讀寫鎖的使用及說明

    關(guān)于ReadWriteLock讀寫鎖的使用及說明

    這篇文章主要介紹了關(guān)于ReadWriteLock讀寫鎖的使用及說明,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-06-06

最新評論

二手房| 信阳市| 柞水县| 施甸县| 隆子县| 饶河县| 会泽县| 洮南市| 洪江市| 营山县| 丘北县| 泰顺县| 荥经县| 上饶县| 广德县| 罗山县| 金门县| 栾川县| 余干县| 泉州市| 项城市| 郯城县| 响水县| 南靖县| 桓仁| 多伦县| 固原市| 保德县| 鄂托克前旗| 金沙县| 吉林市| 伊春市| 宣威市| 辽中县| 新津县| 章丘市| 温宿县| 肃宁县| 新邵县| 开鲁县| 定南县|