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

Java多線程之CAS機制詳解

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

1. 什么是CAS?

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

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

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

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

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

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

2. CAS的應用

2.1 實現原子類

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

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

因此 AtomicInteger 類常見的方法有:

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

當然,Atomic + 其他“數值”包裝類也能使用以上方法!

代碼案例,不使用 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的值
    }

 運行后打印:

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

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

假設 getAndIncrement 方法被兩個線程同時調用,線程1 和 線程2 的 oldValue 值都為 0,內存中的 value 值為0。

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

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

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

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

2.2 實現自旋鎖

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

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

CAS 自旋鎖體現:一段 偽代碼 :

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

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

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

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

3. CAS的ABA問題

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

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

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

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

3.1 ABA問題可能引起的BUG

ABA 問題,就是 CAS 機制導致的數據反復橫跳。

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

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

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

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

3.2 解決ABA問題

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

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

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

根據以下 偽代碼 來理解:

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 作為版本號,當 version 版本號等于讀到的 oldVersion 版本號,則把 oldVersion +1 賦值給 version,并且 num ++ 。這樣就能避免 ABA 問題的出現。

當然,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操作也可以正常進行,不會出現誤判現象。

談談你對 CAS 機制的理解?

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

ABA 問題如何解決?

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

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

相關文章

  • Java8中的Lambda表達式理解與使用示例詳解

    Java8中的Lambda表達式理解與使用示例詳解

    Lambda表達式是Java8引入的一種簡潔寫法,用于實現函數式接口,可以簡化匿名函數的代碼,本文給大家介紹Java8中的Lambda表達式理解與使用示例,感興趣的朋友一起看看吧
    2024-11-11
  • 詳細分析Java中String、StringBuffer、StringBuilder類的性能

    詳細分析Java中String、StringBuffer、StringBuilder類的性能

    在Java中,String類和StringBuffer類以及StringBuilder類都能用于創(chuàng)建字符串對象,而在分別操作這些對象時我們會發(fā)現JVM執(zhí)行它們的性能并不相同,下面我們就來詳細分析Java中String、StringBuffer、StringBuilder類的性能
    2016-05-05
  • java編程兩種樹形菜單結構的轉換代碼

    java編程兩種樹形菜單結構的轉換代碼

    這篇文章主要介紹了java編程兩種樹形菜單結構的轉換代碼,首先介紹了兩種樹形菜單結構的代碼,然后展示了轉換器實例代碼,最后分享了相關實例及結果演示,具有一定借鑒價值,需要的朋友可以了解下。
    2017-12-12
  • IDEA如何一鍵部署SpringBoot項目到服務器

    IDEA如何一鍵部署SpringBoot項目到服務器

    文章介紹了如何在IDEA中部署SpringBoot項目到服務器,使用AlibabaCloudToolkit插件進行配置部署,步驟包括設置服務名稱、選擇文件上傳類型、選擇jar文件、添加服務器信息、輸入上傳路徑、選擇上傳后執(zhí)行的腳本以及執(zhí)行前的操作命令
    2024-12-12
  • Java獲取e.printStackTrace()打印的信息方式

    Java獲取e.printStackTrace()打印的信息方式

    這篇文章主要介紹了Java獲取e.printStackTrace()打印的信息方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • Java與Scala創(chuàng)建List與Map的實現方式

    Java與Scala創(chuàng)建List與Map的實現方式

    這篇文章主要介紹了Java與Scala創(chuàng)建List與Map的實現方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-10-10
  • java發(fā)送http請求并獲取狀態(tài)碼的簡單實例

    java發(fā)送http請求并獲取狀態(tài)碼的簡單實例

    下面小編就為大家?guī)硪黄猨ava發(fā)送http請求并獲取狀態(tài)碼的簡單實例。小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-05-05
  • Struts2實現自定義攔截器的三種方式詳解

    Struts2實現自定義攔截器的三種方式詳解

    這篇文章主要介紹了Struts2實現自定義攔截器的三種方式詳解,一些與系統(tǒng)邏輯相關的通用功能如權限的控制和用戶登錄控制等,需要通過自定義攔截器實現,本節(jié)將詳細講解如何自定義攔截器,需要的朋友可以參考下
    2023-07-07
  • Java使用poi做加自定義注解實現對象與Excel相互轉換

    Java使用poi做加自定義注解實現對象與Excel相互轉換

    這篇文章主要介紹了Java使用poi做加自定義注解實現對象與Excel相互轉換,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-05-05
  • SpringBoot-Admin實現微服務監(jiān)控+健康檢查+釘釘告警

    SpringBoot-Admin實現微服務監(jiān)控+健康檢查+釘釘告警

    本文主要介紹了SpringBoot-Admin實現微服務監(jiān)控+健康檢查+釘釘告警,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-10-10

最新評論