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

Java中的CAS無鎖機(jī)制實(shí)現(xiàn)原理詳解

 更新時(shí)間:2024年01月13日 10:10:07   作者:java架構(gòu)師-太陽  
這篇文章主要介紹了Java中的CAS無鎖機(jī)制實(shí)現(xiàn)原理詳解,無鎖機(jī)制,是樂觀鎖的一種實(shí)現(xiàn),并發(fā)情況下保證對共享變量值更改的原子性,CAS是Java中Unsafe類里面的方法,底層通過調(diào)用C語言接口,再通過cup硬件指令保證原子性,需要的朋友可以參考下

CAS(Compare And Swap) 比較和替換

無鎖機(jī)制,是樂觀鎖的一種實(shí)現(xiàn)

并發(fā)情況下保證對共享變量值更改的原子性 CAS是Java中Unsafe類里面的方法 底層通過調(diào)用C語言接口,再通過cup硬件指令保證原子性

實(shí)現(xiàn)算法

三個(gè)參數(shù)(V,E,N): V是要更新的變量,E是預(yù)期值,N是新值。當(dāng)V的值等于E值時(shí),將V的值設(shè)為N,若V值和E值不同,則說明已經(jīng)有其他線程做了更新,當(dāng)前線程方式自旋重來。

最后,CAS返回當(dāng)前V的真實(shí)值,CAS 一定要volatile變量配合,這樣才能保證每次拿到的變量是主內(nèi)存中最新的那個(gè)值

使用樂觀鎖思想,多個(gè)線程用CAS操作一個(gè)變量時(shí),只有一個(gè)成功且成功更新。

失敗的線程不會(huì)被掛起,僅是被告知失敗,允許再次嘗試,也允許失敗線程放棄操作?;谶@樣的原理,CAS操作即使沒有鎖,也可以發(fā)現(xiàn)其他線程對當(dāng)前線程的干擾,并進(jìn)行恰當(dāng)?shù)奶幚?/p>

與鎖相比,使用CAS會(huì)使程序看起來更加復(fù)雜一些,但由于其非阻塞性,對死鎖天生免疫,且線程間的相互影響也遠(yuǎn)遠(yuǎn)比基于鎖的方式要小,無鎖方式?jīng)]有鎖競爭,也沒有線程間頻繁調(diào)度的開銷。

因此,比基于鎖的方式擁有更優(yōu)越的性能

底層實(shí)現(xiàn)

通過硬件保證了比較更新的原子性和可見性,實(shí)現(xiàn)方式是基于硬件平臺(tái)的匯編指令,大部分的現(xiàn)代處理器都已經(jīng)支持原子化的CAS指令,在JDK 5.0以后,虛擬機(jī)便可以使用這個(gè)指令來實(shí)現(xiàn)并發(fā)操作和并發(fā)數(shù)據(jù)結(jié)構(gòu),CAS是cup的原子指令(cmpxchg),不會(huì)造成數(shù)據(jù)不一致,執(zhí)行cmpxchg指令時(shí),會(huì)判斷當(dāng)前系統(tǒng)是否是多核系統(tǒng),如果是就給總線加鎖,只會(huì)有一個(gè)線程給總線加鎖成功,加速成功之后會(huì)執(zhí)行cas操作,也就是cas的原子性實(shí)際上是cup實(shí)現(xiàn)的,比起synchronized,排他時(shí)間很短,多線程下性能高

優(yōu)點(diǎn)

(1) 高并發(fā)的情況下,比有鎖的程序擁有更好的性能,是輕量級(jí)鎖

(2) 它天生就是死鎖免疫的

(3) 線程不會(huì)阻塞(線程阻塞到喚醒運(yùn)行成本較高),一直處于用戶態(tài)

缺點(diǎn)

(1) 若一直獲取不到鎖死循環(huán),消耗cup資源,可能導(dǎo)致cup飆高(需要控制次數(shù))   

(2) ABA問題:若內(nèi)存地址V初次讀取的值是A,在CAS等待期間它的值曾經(jīng)被改成了B,后來又被改回為A,那CAS操作就會(huì)誤認(rèn)為它從來沒有被改變過。解決辦法,加版本號(hào)屬性,每次值變化了給版本號(hào)加1,。ABA的問題對結(jié)果沒有影響,只會(huì)和CAS概念沖突了

(3) 不能保證代碼塊的原子性:CAS只保證一個(gè)變量的原子性,若更新多變量同時(shí)原子性要用synchronized

適用場景

CAS 適合簡單對象的操作,比如布爾值、整型值等;

典型的使用場景有兩個(gè)

 (1)   J.U.C里面Atomic的原子實(shí)現(xiàn),比如AtomicInteger,AtomicLong。

 (2)   實(shí)現(xiàn)多線程對共享資源競爭的互斥性質(zhì),比如在AQS/ConcurrentHashMap/ConcurrentLinkedQueue等

通常和自旋鎖同時(shí)使用

代碼示例

有一個(gè)成員變量state,默認(rèn)值是0, 定義了一個(gè)方法doSomething(),判斷state是否為0 ,如果為0,就修改成1。

這個(gè)邏輯在多線程環(huán)境下,會(huì)存在原子性的問題,因?yàn)檫@里是一個(gè)典型的,Read - Write的操作。

一般會(huì)在doSomething()這個(gè)方法上加同步鎖來解決原子性問題, 但加同步鎖,會(huì)帶來性能上的損耗,

public class Example {
    private int state = 0;
    public void doSomething() {
        if (state == 0) {
            state = 1;
        }
    }
}

使用CAS機(jī)制來進(jìn)行優(yōu)化

調(diào)用Unsafe類的objectFieldOffset()傳入類的變量得到變量在內(nèi)存中的偏移量

調(diào)用Unsafe類的compareAndSwapInt()方法進(jìn)行更新,傳入四個(gè)參數(shù):當(dāng)前對象實(shí)例/變量在內(nèi)存地址中的偏移量/預(yù)期值/更新值

比較變量內(nèi)存地址偏移量對應(yīng)的值和傳入的預(yù)期值是否相等,若相等,修改內(nèi)存地址中變量的值為要更新的值,否則,返回false

compareAndSwap()是native方法,底層實(shí)現(xiàn)中,在多核CPU環(huán)境下,會(huì)增加一個(gè)Lock指令對緩存或總線加鎖,從而保證比較并替換這兩個(gè)指令的原子性。

這里注意變量要加volatile

public class Example {
    
    private volatile int state = 0;
    private static final Unsafe UNSAFE = Unsafe.getUnsafe();
    private static final long stateOffset;

    static {
        try {
            stateOffset = UNSAFE.objectFieldOffset(Example.class.getDeclaredField("state"));
        } catch (Exception ex){
            throw new Error(ex);
        }
    }

    public void doSomething() {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, 0 ,1)) {
            // TODO
        }
    }

    public int getState() {
        return state;
    }
}
public static void main(String[] args) {
    Example example = new Example();
    example.doSomething();
    System.out.println(example.getState());
}

測試的時(shí)候會(huì)報(bào)錯(cuò)會(huì)在這行代碼 Unsafe.getUnsafe();

Caused by: java.lang.SecurityException: Unsafe
    at sun.misc.Unsafe.getUnsafe(Unsafe.java:90)
    at com.test.Example.<clinit>(Example.java:7)
    ... 1 more

查看Unsafe.getUnsafe()源碼

@CallerSensitive
public static Unsafe getUnsafe() {
    Class var0 = Reflection.getCallerClass();
    if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
        throw new SecurityException("Unsafe");
    } else {
        return theUnsafe;
    }
}

如果不是systemClassLoader則會(huì)拋出SecurityException(“Unsafe”)異常,所以用戶編寫的程序使用不了unsafe實(shí)例。

怎么解決ABA問題,加一個(gè)版本號(hào) 單線程情況下

public static void main(String[] args) {

    Book javaBook = new Book(1,"javaBook");

    // 參數(shù)1是版本號(hào)
    AtomicStampedReference<Book> stampedReference = new AtomicStampedReference<>(javaBook,1);

    System.out.println(stampedReference.getReference() + "\t" + stampedReference.getStamp());

    Book mysqlBook = new Book(2,"mysqlBook");

    boolean b;
    // 在判斷的時(shí)候不斷要判斷book,還要判斷版本號(hào),更新的時(shí)候更新book的值,也要更新版本號(hào)(這里是加1)
    b = stampedReference.compareAndSet(javaBook, mysqlBook, stampedReference.getStamp(), stampedReference.getStamp() + 1);

    System.out.println(b + "\t" + stampedReference.getReference() + "\t" + stampedReference.getStamp());

    b = stampedReference.compareAndSet(mysqlBook, javaBook, stampedReference.getStamp(), stampedReference.getStamp() + 1);

    System.out.println(b+"\t"+stampedReference.getReference()+"\t"+stampedReference.getStamp());

}

對線程情亂下

public class ABADemo {
    static AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(100,1);
    public static void main(String[] args) {
        new Thread(() -> {
            int stamp = stampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "\t" + "首次版本號(hào):" + stamp);
            // 暫停500毫秒,保證后面的t4線程初始化拿到的版本號(hào)和我一樣
            try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
            // 從100改到101
            stampedReference.compareAndSet(100,101,stampedReference.getStamp(),stampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"\t"+"2次流水號(hào):"+stampedReference.getStamp());
            // 從101改到100
            stampedReference.compareAndSet(101,100,stampedReference.getStamp(),stampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"\t"+"3次流水號(hào):"+stampedReference.getStamp());
        },"t3").start();
        new Thread(() -> {
            int stamp = stampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+ "\t" + "首次版本號(hào):" + stamp);
            // 暫停1秒鐘線程,等待上面的t3線程,發(fā)生了ABA問題
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
            boolean b = stampedReference.compareAndSet(100, 2022, stamp, stamp + 1);
            System.out.println(b+"\t"+stampedReference.getReference()+"\t"+stampedReference.getStamp());
        },"t4").start();
    }
}

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

相關(guān)文章

  • springboot自定義yml配置文件及其外部部署過程

    springboot自定義yml配置文件及其外部部署過程

    這篇文章主要介紹了springboot自定義yml配置文件及其外部部署過程,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • SpringCloud的@RefreshScope 注解你了解嗎

    SpringCloud的@RefreshScope 注解你了解嗎

    這篇文章主要介紹了Spring Cloud @RefreshScope 原理及使用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-09-09
  • JAVA  字符串加密、密碼加密實(shí)現(xiàn)方法

    JAVA 字符串加密、密碼加密實(shí)現(xiàn)方法

    這篇文章主要介紹了JAVA 字符串加密、密碼加密實(shí)現(xiàn)方法的相關(guān)資料,需要的朋友可以參考下
    2016-10-10
  • Feign調(diào)用傳輸文件異常的解決

    Feign調(diào)用傳輸文件異常的解決

    這篇文章主要介紹了Feign調(diào)用傳輸文件異常的解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-06-06
  • java算法題解LeetCode30包含min函數(shù)的棧實(shí)例

    java算法題解LeetCode30包含min函數(shù)的棧實(shí)例

    這篇文章主要為大家介紹了java算法題解LeetCode30包含min函數(shù)的棧實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-01-01
  • SpringBoot+Netty+WebSocket實(shí)現(xiàn)消息發(fā)送的示例代碼

    SpringBoot+Netty+WebSocket實(shí)現(xiàn)消息發(fā)送的示例代碼

    這篇文章主要介紹了SpringBoot+Netty+WebSocket實(shí)現(xiàn)消息發(fā)送的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09
  • SpringBoot 整合Jest實(shí)例代碼講解

    SpringBoot 整合Jest實(shí)例代碼講解

    本文通過實(shí)例代碼給大家介紹了SpringBoot 整合Jest的相關(guān)知識(shí),非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2018-08-08
  • 使用Java和WebSocket實(shí)現(xiàn)網(wǎng)頁聊天室實(shí)例代碼

    使用Java和WebSocket實(shí)現(xiàn)網(wǎng)頁聊天室實(shí)例代碼

    WebSocket是HTML5一種新的協(xié)議,它實(shí)現(xiàn)了瀏覽器與服務(wù)器全雙工通信,這里就將使用WebSocket來開發(fā)網(wǎng)頁聊天室,對Java和WebSocket實(shí)現(xiàn)網(wǎng)頁聊天室的實(shí)例代碼感興趣的朋友一起學(xué)習(xí)吧
    2016-06-06
  • SpringCloud Gateway網(wǎng)關(guān)功能介紹與使用

    SpringCloud Gateway網(wǎng)關(guān)功能介紹與使用

    SpringCloud Gateway 是 Spring Cloud 的一個(gè)全新項(xiàng)目,它旨在為微服務(wù)架構(gòu)提供一種簡單有效的統(tǒng)一的 API 路由管理方式。這篇文章主要介紹了SpringCloud Gateway網(wǎng)關(guān)作用,需要的朋友可以參考下
    2022-12-12
  • Spring boot 整合 Okhttp3 并封裝請求工具實(shí)例 詳解

    Spring boot 整合 Okhttp3 并封裝請求工具實(shí)例 詳解

    OkHttp作為一款成熟、穩(wěn)定、易用的HTTP客戶端庫,擁有較高的性能和多樣化的功能,已被廣泛應(yīng)用于移動(dòng)應(yīng)用開發(fā)、Web服務(wù)端開發(fā)等領(lǐng)域,這篇文章主要介紹了Spring boot 整合 Okhttp3 并封裝請求工具,需要的朋友可以參考下
    2023-08-08

最新評(píng)論