国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

JUC并發(fā)編程16 | CAS自旋鎖

這篇具有很好參考價值的文章主要介紹了JUC并發(fā)編程16 | CAS自旋鎖。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點(diǎn)擊"舉報違法"按鈕提交疑問。

CAS自旋鎖

是什么,干什么,解決了什么痛點(diǎn)?如何解決,如何使用。

原子類:java.util.concurrent.atomic

在沒有CAS之前,多線程環(huán)境不使用原子類保證線程安全i++等操作,會出現(xiàn)數(shù)據(jù)問題,如果直接加鎖synchronized,資源的開銷就比較大

在出現(xiàn)CAS之后,多線程環(huán)境,使用原子類保證線程安全i++,類似我們的樂觀鎖

CAS是什么

CAS是compare and swap的縮寫,中文翻譯為比較并交換,實(shí)現(xiàn)并發(fā)算法時常用的一種技術(shù)

CAS 包含三個操作數(shù) —— 內(nèi)存位置、預(yù)期原值及更新值

在執(zhí)行CAS操作的時候,將內(nèi)存位置的值與預(yù)期原值比較,

  • 如果相匹配,那么處理器會自動將該位置值更新為新值
  • 如果不匹配,處理器不做任何操作,多個線程同時執(zhí)行CAS只有一個會成功

CAS的原理

CAS 有三個操作數(shù),位置內(nèi)存值V,舊的預(yù)期值A(chǔ),要修改的更新值為B

當(dāng)且僅當(dāng)就得預(yù)期值A(chǔ)與內(nèi)存值V相同時,將內(nèi)存值V修改位B,否則什么都不做,重來——即自旋

JUC并發(fā)編程16 | CAS自旋鎖

這是通過硬件級別保證的

Unsafe 類

CAS是JDK提供的非阻塞原子性操作,它通過硬件保證了比較-更新的原子性。

它是非阻塞的且自身具有原子性,也就是說這玩意效率更高且通過硬件保證,說明這玩意更可靠。

CAS是一條CPU的原子指令(cmpxchg指令),不會造成所謂的數(shù)據(jù)不一致問題,Unsafe提供的CAS方法(如compareAndSwapXXX)底層實(shí)現(xiàn)即為CPU指令cmpxchg。

執(zhí)行cmpxchg指令的時候,會判斷當(dāng)前系統(tǒng)是否為多核系統(tǒng),如果是就給總線加鎖,只有一個線程會對總線加鎖成功,加鎖成功之后會執(zhí)行cas操作,也就是說CAS的原子性實(shí)際上是CPU實(shí)現(xiàn)獨(dú)占的,比起用synchronized重量級鎖,這里的排他時間要短很多,所以在多線程情況下性能會比較好。

進(jìn)入Unsafe方法查看源碼

public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

/**
上面三個方法都是類似的,主要對4個參數(shù)做一下說明。
var1:表示要操作的對象
var2:表示要操作對象中屬性地址的偏移量
var4:表示需要修改數(shù)據(jù)的期望的值
var5/var6:表示需要修改為的新值
*/

1 Unsafe
是CAS的核心類,由于Java方法無法直接訪問底層系統(tǒng),需要通過本地〈native)方法來訪問,Unsafe相當(dāng)于一個后門,基于該類可以直接操作特定內(nèi)存的數(shù)據(jù)。Unsafe類存在于sun.misc包中,共內(nèi)部方法操作可以像C的指針一樣直接操作內(nèi)存,因?yàn)镴ava中CAS操作的執(zhí)行依賴于Unsafe類的方法。

注意Unsafe類中的所有方法都是native修飾的,也就是說Unsafe類中的方法都直接調(diào)用操作系統(tǒng)底層資源執(zhí)行相應(yīng)任務(wù)

2 變量valueOffset,表示該變量值在內(nèi)存中的偏移地址,因?yàn)閁nsafe就是根據(jù)內(nèi)存偏移地址獲取數(shù)據(jù)的。

// AtomicInteger 類
public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}

// Unsafe 類
public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        // volatile 修飾,一旦var5被修改會被立即獲知
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}

3 變量value使用volatile修飾,保證了多線程之間的內(nèi)存可見性

假設(shè)線程A和線程B兩個線程同時執(zhí)行g(shù)etAndAddInt操作(分別跑在不同CPU上)

  1. AtomicInteger里面的value原始值為3,即主內(nèi)存中AtomicInteger的value為3,根據(jù)JMM模型,線程A和線程B各自持有一份值為3的value的副本分別到各自的工作內(nèi)存。
  2. 線程A通過getIntVolatile(var1, var2)拿到value值3,這時線程A被掛起。
  3. 線程B也通過getlntVolatile(var1, var2)方法獲取到value值3,此時剛好線程B沒有被掛起并執(zhí)行compareAndSwaplnt方法比較內(nèi)存值也為3,成功修改內(nèi)存值為4,線程B打完收工,一切OK。
  4. 這時線程A恢復(fù),執(zhí)行compareAndSwapInt方法比較,發(fā)現(xiàn)自己手里的值數(shù)字3和主內(nèi)存的值數(shù)字4不一致,說明該值已經(jīng)被其它線程搶先一步修改過了,那A線程本次修改失敗,只能重新讀取重新來一遍了。
  5. 線程A重新獲取value值,因?yàn)樽兞縱alue被volatile修飾,所以其它線程對它的修改,線程A總是能夠看到,線程A繼續(xù)執(zhí)行compareAndSwapInt進(jìn)行比較替換,直到成功。

原子引用AtomicReference

public class CASDemo {
    public static void main(String[] args) {
        AtomicReference<User> atomicReference = new AtomicReference<>();
        User zhangsan = new User("zhangsan", 22);
        User lisi = new User("lisi", 24);
        atomicReference.set(zhangsan);
        System.out.println(atomicReference.compareAndSet(zhangsan,lisi)+"\t" + atomicReference.get().toString());
        System.out.println(atomicReference.compareAndSet(zhangsan,lisi)+"\t" + atomicReference.get().toString());
    }
}

CAS與自旋鎖

通過cas操作完成自旋鎖,A線程先進(jìn)來,調(diào)用lock方法自己持有鎖5秒;

B隨后進(jìn)來發(fā)現(xiàn)當(dāng)前線程支持有所,進(jìn)行自旋等待,直到A釋放鎖后B隨后搶到

public class CASDemo {
    AtomicReference<Thread> atomicReference = new AtomicReference<>();
    public void lock(){
        Thread thread = Thread.currentThread();
        System.out.println("==============="+Thread.currentThread().getName()+" come in ==============");
        while (!atomicReference.compareAndSet(null,thread)) {}
    }
    public void unlock(){
        Thread thread =  Thread.currentThread();
        atomicReference.compareAndSet(thread,null);
        System.out.println("==============="+Thread.currentThread().getName()+" task is over ==============");
    }
    public static void main(String[] args) throws InterruptedException {
        CASDemo casDemo = new CASDemo();
        new Thread(()->{
            casDemo.lock();
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                casDemo.unlock();
            }
        },"t1").start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(()->{
            casDemo.lock();
            try {
                TimeUnit.SECONDS.sleep(8);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                casDemo.unlock();
            }
        },"t2").start();
    }
}

CAS的缺點(diǎn)

  • 循環(huán)時間長開銷大
  • 具有ABA問題

循環(huán)時間長開銷大

如果cas失敗,會一直進(jìn)行嘗試。如果cas長時間一直不成功,可能會給cpu帶來很大的開銷

ABA問題

CAS會導(dǎo)致“ABA問題”。

CAS算法實(shí)現(xiàn)一個重要前提需要取出內(nèi)存中某時刻的數(shù)據(jù)并在當(dāng)下時刻比較并替換,那么在這個時間差類會導(dǎo)致數(shù)據(jù)的變化。

比如說一個線程1從內(nèi)存位置V中取出A,這時候另一個線程2也從內(nèi)存中取出A,并且線程2進(jìn)行了一些操作將值變成了B,然后線程2又將V位置的數(shù)據(jù)變成A,這時候線程1進(jìn)行CAS操作發(fā)現(xiàn)內(nèi)存中仍然是A,預(yù)期OK,然后線程1操作成功。

盡管線程1的CAS操作成功,但是不代表這個過程就是沒有問題的。

@Data
@AllArgsConstructor
@NoArgsConstructor
class Book{
    private String name;
    private int id;
}
public class ABADemo {

    public static void main(String[] args) {

        Book java = new Book("java",1);
        Book mysql = new Book("mysql",2);
        AtomicStampedReference<Book> stampedReference = new AtomicStampedReference<>(java, 1);
        new Thread(()->{
            // 初始條件是java
            System.out.println(stampedReference.getReference()+"\t初始條件是java:" + stampedReference.getStamp());
            // 此時郵戳莫有啟動,但是已經(jīng)被改為mysql了
            stampedReference.compareAndSet(java, mysql, stampedReference.getStamp(), stampedReference.getStamp());
            System.out.println(stampedReference.getReference()+"\t被改為mysql了" + stampedReference.getStamp());
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 修改回Java了
            stampedReference.compareAndSet(mysql, java, stampedReference.getStamp(), stampedReference.getStamp());
            System.out.println(stampedReference.getReference()+"\t修改回Java了:" + stampedReference.getStamp());
        },"t1").start();
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 不知道被就該過了
            stampedReference.compareAndSet(java, mysql, stampedReference.getStamp(), stampedReference.getStamp());
            System.out.println(stampedReference.getReference()+"\t不知道被就該過了,此時還能改為mysql" + stampedReference.getStamp());

        },"t2").start();

    }
}
/**
Book(name=java, id=1)	初始條件是java:1
Book(name=mysql, id=2)	被改為mysql了1
Book(name=java, id=1)	修改回Java了:1
Book(name=mysql, id=2)	不知道被就該過了,此時還能改為mysql1
*/

解決:ABA

使用 AtomicStampedReference

內(nèi)容 版本
A 1
B 2
A 3

解決代碼

@Data
@AllArgsConstructor
@NoArgsConstructor
class Book{
    private String name;
    private int id;
}
public class ABADemo {
    public static void main(String[] args) {
        Book java = new Book("java",1);
        Book mysql = new Book("mysql",2);
        AtomicStampedReference<Book> stampedReference = new AtomicStampedReference<>(java, 1);
        System.out.println(stampedReference.getReference()+"\t" + stampedReference.getStamp());
        boolean b;
        // 如果是java,且郵戳不變,那就換成mysql,同時郵戳+1
        b = stampedReference.compareAndSet(java, mysql, stampedReference.getStamp(), stampedReference.getStamp()+1);
        System.out.println(stampedReference.getReference()+"\t" + stampedReference.getStamp());
        // 把 java 換回來
        b = stampedReference.compareAndSet(mysql, java, stampedReference.getStamp(), stampedReference.getStamp()+1);
        System.out.println(stampedReference.getReference()+"\t" + stampedReference.getStamp());
    }
}

上面演示了單線程的情況,下面演示多線程的cas情況文章來源地址http://www.zghlxwxcb.cn/news/detail-443113.html

@Data
@AllArgsConstructor
@NoArgsConstructor
class Book{
    private String name;
    private int id;
}
public class ABADemo {

    public static void main(String[] args) {

        Book java = new Book("java",1);
        Book mysql = new Book("mysql",2);
        AtomicStampedReference<Book> stampedReference = new AtomicStampedReference<>(java, 1);
        new Thread(()->{
            int stamp = stampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+"\t首次版本號:" + stampedReference.getStamp());
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 此時郵戳莫有啟動,但是已經(jīng)被改為mysql了
            stampedReference.compareAndSet(java, mysql, stampedReference.getStamp(), stampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"\t"+stampedReference.getReference()+"\t版本號2:" + stampedReference.getStamp());

            // 修改回Java了
            stampedReference.compareAndSet(mysql, java, stampedReference.getStamp(), stampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"\t"+stampedReference.getReference()+"\t版本號3:" + stampedReference.getStamp());
        },"t1").start();
        new Thread(()->{
            int stamp = stampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+"\t首次版本號:" + stampedReference.getStamp());
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 被修改過了
            boolean b = stampedReference.compareAndSet(java, mysql, stamp, stampedReference.getStamp()+1);
            System.out.println(b+"\t"+stampedReference.getReference()+"\t" + stampedReference.getStamp());

        },"t2").start();
    }
}
/**

t1	首次版本號:1
t2	首次版本號:1
t1	Book(name=mysql, id=2)	版本號2:2
t1	Book(name=java, id=1)	版本號3:3
false	Book(name=java, id=1)	3
*/

到了這里,關(guān)于JUC并發(fā)編程16 | CAS自旋鎖的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請點(diǎn)擊違法舉報進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • DP讀書:不知道干什么就和我一起讀書吧——以《鯤鵬處理器 架構(gòu)與編程》中鯤鵬軟件的構(gòu)成為例

    DP讀書:不知道干什么就和我一起讀書吧——以《鯤鵬處理器 架構(gòu)與編程》中鯤鵬軟件的構(gòu)成為例

    雖然清楚知識需要靠時間沉淀,但在看到自己做不出來的題別人會做,自己寫不出的代碼別人會寫時還是會感到焦慮怎么辦? 你是否也因?yàn)樽陨砀車说牟罹喽a(chǎn)生過迷茫,這份迷茫如今是被你克服了還是仍舊讓你感到困擾?來分享一下吧! 我就讀了幾天書,就這樣了。

    2024年02月09日
    瀏覽(22)
  • AI(Artificial Intelligence)解決方案工程師是干什么的? Solution Engineer

    作者:禪與計算機(jī)程序設(shè)計藝術(shù) 1.簡介 從事智能解決方案開發(fā)、架構(gòu)設(shè)計、產(chǎn)品開發(fā)等工作。主要負(fù)責(zé)智能業(yè)務(wù)系統(tǒng)的研發(fā)及項目管理。 本人擁有豐富的產(chǎn)品研發(fā)經(jīng)驗(yàn),包括傳統(tǒng)IT行業(yè)的軟件開發(fā)經(jīng)驗(yàn)、數(shù)據(jù)庫設(shè)計、業(yè)務(wù)需求分析、項目管理、團(tuán)隊合作等方面。在智元的工作

    2024年02月08日
    瀏覽(26)
  • 《JUC并發(fā)編程 - 高級篇》05 -共享模型之無鎖 (CAS | 原子整數(shù) | 原子引用 | 原子數(shù)組 | 字段更新器 | 原子累加器 | Unsafe類 )

    《JUC并發(fā)編程 - 高級篇》05 -共享模型之無鎖 (CAS | 原子整數(shù) | 原子引用 | 原子數(shù)組 | 字段更新器 | 原子累加器 | Unsafe類 )

    有如下需求,保證 account.withdraw 取款方法的線程安全 原有實(shí)現(xiàn)并不是線程安全的 測試代碼 執(zhí)行測試代碼,某次執(zhí)行結(jié)果 5.1.1 為么不安全 withdraw 方法是臨界區(qū),會存在線程安全問題 查看下字節(jié)碼 多線程在執(zhí)行過程中可能會出現(xiàn)指令的交錯,從而結(jié)果錯誤! 5.1.2 解決思路1

    2023年04月12日
    瀏覽(19)
  • 【并發(fā)編程】CAS到底是什么

    Java實(shí)現(xiàn)CAS的原理 | Java程序員進(jìn)階之路 美團(tuán)終面:CAS確定完全不需要鎖嗎? CAS 是 Compare-And-Swap (比較并交換)的縮寫,是一種 輕量級的同步機(jī)制 ,主要用于實(shí)現(xiàn)多線程環(huán)境下的無鎖算法和數(shù)據(jù)結(jié)構(gòu),保證了并發(fā)安全性。它可以在 不使用鎖 (如synchronized、Lock)的情況下,對

    2024年02月20日
    瀏覽(23)
  • 【Docker】什么是Docker,它用來干什么

    【Docker】什么是Docker,它用來干什么

    作者簡介: 辭七七,目前大一,正在學(xué)習(xí)C/C++,Java,Python等 作者主頁: 七七的個人主頁 文章收錄專欄: 七七的閑談 歡迎大家點(diǎn)贊 ?? 收藏 ? 加關(guān)注哦!???? Docker 是一個開源的應(yīng)用容器引擎,讓開發(fā)者可以打包他們的應(yīng)用以及依賴包到一個可移植的鏡像中,然后發(fā)布到

    2024年02月07日
    瀏覽(50)
  • 數(shù)字藏品可以用來干什么?

    數(shù)字藏品可以用來干什么?

    一、作為數(shù)字收藏藝術(shù)品,滿足收藏者的愛好。繪畫、文物等藝術(shù)品是數(shù)字收藏品是最基礎(chǔ)的應(yīng)用,也是目前最受歡迎的種類,它與現(xiàn)實(shí)生活中的其他藝術(shù)品具有相似性,一樣通過網(wǎng)上購買的方式獲得。 數(shù)字藏品,雖然“摸不著”,但與傳統(tǒng)藝術(shù)品相比較,又具有一定優(yōu)勢,

    2024年02月09日
    瀏覽(23)
  • 大數(shù)據(jù)是干什么的?

    大數(shù)據(jù)技術(shù)的戰(zhàn)略意義不在于掌握龐大的數(shù)據(jù)信息,而在于對這些有意義的數(shù)據(jù)進(jìn)行專業(yè)的處理。換句話說,如果把大數(shù)據(jù)比作一個行業(yè),這個行業(yè)盈利的關(guān)鍵在于提高數(shù)據(jù)的“處理能力”,通過“處理”實(shí)現(xiàn)數(shù)據(jù)的“增值”。 從技術(shù)上講,大數(shù)據(jù)和云計算的關(guān)系就像硬幣的

    2024年01月21日
    瀏覽(24)
  • 服務(wù)器是什么?它是用來干什么的?

    服務(wù)器是什么?它是用來干什么的?

    作者: Insist-- 個人主頁: insist--個人主頁 作者會持續(xù)更新網(wǎng)絡(luò)知識和python基礎(chǔ)知識,期待你的關(guān)注 ? 目錄 一、服務(wù)器是什么? 二、服務(wù)器的作用 1、提高訪問速度 2、提高安全性 三、云服務(wù)器與物理服務(wù)器 1、云服務(wù)器 云服務(wù)器的優(yōu)點(diǎn): 2、物理服務(wù)器 物理服務(wù)器的優(yōu)點(diǎn):

    2024年02月08日
    瀏覽(22)
  • 什么是tomcat?tomcat是干什么用的?

    什么是tomcat?tomcat是干什么用的?

    什么是tomcat Tomcat是常見的免費(fèi)的web服務(wù)器. Tomcat 這個名字的來歷,Tomcat是一種野外的貓科動物,不依賴人類,獨(dú)立生活。 Tomcat的作者,取這個名字的初衷是希望,這一款服務(wù)器可以自力更生,自給自足,像Tomcat這樣一種野生動物一般,不依賴其他插件,而可以獨(dú)立達(dá)到提供

    2023年04月11日
    瀏覽(24)
  • python cv2是什么,可以用來干什么

    OpenCV (Open Source Computer Vision Library) 是一個流行的開源計算機(jī)視覺庫,提供了豐富的圖像和視頻處理功能。通過使用 OpenCV 的 Python 綁定庫 cv2,可以實(shí)現(xiàn)以下一些功能: 圖像讀取和顯示:使用 cv2.imread() 讀取圖像文件,使用 cv2.imshow() 顯示圖像窗口。 圖像處理:包括圖像濾波、

    2024年02月14日
    瀏覽(20)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包