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

Java?CAS與Atomic原子操作核心原理詳解

 更新時(shí)間:2023年04月19日 11:06:21   作者:胡尚  
CAS(Compare?and?Swap)和Atomic原子操作是保證多線程并發(fā)安全的常用機(jī)制,能夠高效地實(shí)現(xiàn)對共享變量的安全訪問和修改,避免線程競爭導(dǎo)致的數(shù)據(jù)不一致和死鎖等問題。它們的應(yīng)用可以提高程序的并發(fā)性能和可維護(hù)性,是多線程編程中的重要工具

什么是原子操作

Mysql事務(wù)中的原子性就是一個(gè)事務(wù)中執(zhí)行的多條sql,要么同時(shí)成功,要么同時(shí)失敗,他們不可拆分。并發(fā)中的原子操作也一樣,多個(gè)線程中,站在線程A的角度看線程B的操作,線程B的操作就是一個(gè)原子的;站在線程B的角度看線程A,線程A的操作是原子的。一整個(gè)操作要么全部執(zhí)行完了,要么就沒有執(zhí)行,中間不能拆分。

那么要怎么實(shí)現(xiàn)原子性嘞?可以使用synchronized鎖來保證一段代碼的原子性,但是加鎖影響性能,甚至還有死鎖方面的問題需要考慮。

所以鎖機(jī)制是比較重量級的,粒度較大的一種機(jī)制,比如對于計(jì)數(shù)器方面的操作來說,可能加鎖的耗時(shí)都比整個(gè)計(jì)算的耗時(shí)還要高。Java 就提供了 Atomic 系列的原子操作類,在java.util.concurrent.atomic包下

這些原子操作類是基于處理器的CAS指令來實(shí)現(xiàn)原子性的,Compare and swap。比較并且交換

CAS

每個(gè)CAS操作過程基本上都包含三個(gè)部分:內(nèi)存地址V、期望值A(chǔ)、新值B

期望值就是舊值,首先會(huì)去內(nèi)存地址中進(jìn)行比較,我期望當(dāng)前這個(gè)內(nèi)存地址中的值是我期望的舊值,如果是則把新值賦值到這個(gè)內(nèi)存地址中,如果不是則不做任何事。在一般的使用中我們會(huì)不斷嘗試去進(jìn)行CAS操作,直到成功為止。

Java 中的 Atomic 系列的原子操作類的實(shí)現(xiàn)則是利用了循環(huán) CAS 來實(shí)現(xiàn)。

使用CAS實(shí)現(xiàn)原子操作的幾個(gè)問題

ABA問題

ABA問題在大多數(shù)場景下,不解決其實(shí)也沒什么影響。

解決思路:添加版本戳,在變量前面追加上版本號(hào),每次變量更新的時(shí)候把版本號(hào)加 1,那么 A-->B-->A 就會(huì)變成 1A-->2B-->3A

循環(huán)時(shí)間長,對于cpu來說開銷較大

只能保證一個(gè)共享變量的原子操作

對于多個(gè)共享變量操作時(shí)就無法使用CAS來保證原子性了,這個(gè)時(shí)候還是需要用鎖。

還有一個(gè)取巧的辦法,就是把多個(gè)共享變量合并成一個(gè)共享變量來操作。比如,有兩個(gè)共享變量 i=2,j=a,合并一下 ij=2a,然后用 CAS 來操作 ij。

從 Java 1.5開始,JDK 提供了AtomicReference類來保證引用對象之間的原子性,就可以把多個(gè)變量放在一個(gè)對象里來進(jìn)行 CAS 操作。

相關(guān)原子操作類的使用

這些類的用戶都大同小異,這里就拿幾個(gè)典型來舉例

AtomicInteger

// 以原子方式將給定值添加到當(dāng)前值,然后將相加后的結(jié)果返回
public final int addAndGet(int delta){}
// 指定期望值與修改后的值,如果期望值和當(dāng)前值相同則進(jìn)行更新操作
public final boolean compareAndSet(int expect, int update) {}
// 先返回當(dāng)前值,然后再進(jìn)行原子自增1
public final int getAndIncrement() {}
// 先返回當(dāng)前值,然后進(jìn)行原子更新操作
public final int getAndSet(int newValue) {}

案例:

public class UseAtomicInt {
    static AtomicInteger ai = new AtomicInteger(10);
    public static void main(String[] args) {
        ai.getAndIncrement();
        ai.incrementAndGet();
        //ai.compareAndSet();
        ai.addAndGet(24);
    }
}

AtomicIntegerArray

提供原子的方式更新數(shù)據(jù)中的整形,常用方法如下:

// 以原子方式將給定值添加到索引 i 處的元素。然后返回更新后的值
public final int addAndGet(int i, int delta){}
// 先比較,期望值和當(dāng)前值相同再執(zhí)行更新操作
public final boolean compareAndSet(int i, int expect, int update) {}

案例:

public class AtomicArray {
    static int[] value = new int[] { 1, 2 };
    static AtomicIntegerArray ai = new AtomicIntegerArray(value);
    public static void main(String[] args) {
        ai.getAndSet(0, 3);
        System.out.println(ai.get(0));
        //原數(shù)組不會(huì)變化
        System.out.println(value[0]);
        }
}
Process finished with exit code 0

// 輸出結(jié)果
3
1

需要注意的是,數(shù)組 value 通過構(gòu)造方法傳遞進(jìn)去,然后 AtomicIntegerArray會(huì)將當(dāng)前數(shù)組復(fù)制一份,所以當(dāng) AtomicIntegerArray 對內(nèi)部的數(shù)組元素進(jìn)行修改 時(shí),不會(huì)影響傳入的數(shù)組。

更新引用類型

如果要同時(shí)更新多個(gè)原子變量就需要使用更新引用類型提供的類了。Atomic提供了三個(gè)類:

AtomicReference

原子更新引用類型

案例:

public class UseAtomicReference {
    public static AtomicReference<UserInfo> atomicUserRef;
    public static void main(String[] args) {
        //要修改的實(shí)體的實(shí)例
        UserInfo user = new UserInfo("Mark", 15);
        atomicUserRef = new AtomicReference(user);
        // 再創(chuàng)建一個(gè)對象
        UserInfo updateUser = new UserInfo("Bill",17);
        // 期望值和當(dāng)前值相同就進(jìn)行修改
        atomicUserRef.compareAndSet(user,updateUser);
        System.out.println(atomicUserRef.get());
        System.out.println(user);
        /*
        	輸出結(jié)果:
            UserInfo{name='Bill', age=17}
            UserInfo{name='Mark', age=15}
		*/
    }
    /**
     * 定義一個(gè)實(shí)體類
     */
    static class UserInfo {
        private volatile String name;
        private int age;
        public UserInfo(String name, int age) {
            this.name = name;
            this.age = age;
        }
        public String getName() {
            return name;
        }
        public int getAge() {
            return age;
        }
        @Override
        public String toString() {
            return "UserInfo{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
}

AtomicStampedReference

利用版本戳的形式記錄了每次改變以后的版本號(hào),這樣的話就不會(huì)存在 ABA問題了

AtomicMarkableReference

原子更新帶有標(biāo)記位的引用類型??梢栽痈乱粋€(gè)布爾類型的標(biāo)記位和引 用類型。

構(gòu)造方法是 AtomicMarkableReference(V initialRef,booleaninitialMark)。

AtomicMarkableReference跟 AtomicStampedReference 差不多,

AtomicStampedReference 是使用 pair 的 int stamp 作為計(jì)數(shù)器使用

AtomicMarkableReference 的使用pair 的boolean mark。

AtomicStampedReference 可能關(guān)心的是動(dòng)過幾次,AtomicMarkableReference 關(guān)心的是有沒有被人動(dòng)過。

案例:

// 第二個(gè)線程,期望的時(shí)間戳和當(dāng)前時(shí)間戳不同,所以更新不成功
public class UseAtomicStampedReference {
    static AtomicStampedReference<String> asr = new AtomicStampedReference("mark", 0);
    public static void main(String[] args) throws InterruptedException {
        //拿到當(dāng)前的版本號(hào)(舊)
        final int oldStamp = asr.getStamp();
        final String oldReference = asr.getReference();
        System.out.println(oldReference + "============" + oldStamp);
        Thread rightStampThread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + ":當(dāng)前變量值:"
                        + oldReference + "-當(dāng)前版本戳:" + oldStamp + "\n"
                        + asr.compareAndSet(oldReference, oldReference + "+Java", oldStamp, oldStamp + 1));
            }
        });
        Thread errorStampThread = new Thread(new Runnable() {
            @Override
            public void run() {
                String reference = asr.getReference();
                System.out.println(Thread.currentThread().getName() + ":當(dāng)前變量值:"
                        + reference + "-當(dāng)前版本戳:" + asr.getStamp() + "\n"
                        + asr.compareAndSet(reference, reference + "+C", oldStamp, oldStamp + 1));
            }
        });
        rightStampThread.start();
        rightStampThread.join();
        errorStampThread.start();
        errorStampThread.join();
        System.out.println(asr.getReference() + "============" + asr.getStamp());
    }
}

輸出結(jié)果

mark============0
Thread-0:當(dāng)前變量值:mark-當(dāng)前版本戳:0
true
Thread-1:當(dāng)前變量值:mark+Java-當(dāng)前版本戳:1
false
mark+Java============1

原子更新字段類

如果需原子地更新某個(gè)類里的某個(gè)字段時(shí),就需要使用原子更新字段類

Atomic 包提供了以下 3 個(gè)類進(jìn)行原子字段更新。 要想原子地更新字段類需要兩步。

因?yàn)樵痈伦侄晤惗际浅橄箢悾?每次使用的時(shí)候必須使用靜態(tài)方法 newUpdater()創(chuàng)建一個(gè)更新器,并且需要設(shè)置想要更新的類和屬性。

更新類的字段(屬性)必須使用 public volatile修飾符。

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

LongAdder

并發(fā)量較少,自旋的沖突也就較少。但如果并發(fā)很多的情況下,CAS機(jī)制就不如synchronized了,因?yàn)楹芏鄠€(gè)線程都集中判斷一個(gè)變量的值,不斷的自旋,對cpu的消耗也較大,同一時(shí)刻又只會(huì)一個(gè)線程更新成功。

在JDK1.8就引入了LongAdder類,它在處理上面問題的時(shí)候是采用的一種熱點(diǎn)數(shù)據(jù)的分散寫

LongAdder中有兩個(gè)成員變量

// 當(dāng)為非空時(shí),大小為 2 的冪。
// 如果并發(fā)很高就使用cell數(shù)組做寫熱點(diǎn)的分散,其中某些線程共同操作某一個(gè)數(shù)組中的元素
transient volatile Cell[] cells;
// 當(dāng)爭搶較少時(shí)使用這個(gè)變量來進(jìn)行cas,就類似于AtomicInteger類中的value變量
transient volatile long base;

然后調(diào)用sum()方法將數(shù)組cells和base變量的中做一個(gè)匯總,返回當(dāng)前總和。在沒有并發(fā)更新的情況下調(diào)用將返回準(zhǔn)確的結(jié)果,但在計(jì)算總和時(shí)發(fā)生的并發(fā)更新可能不會(huì)合并,所以sum()方法并不能保證強(qiáng)一致性,它返回的只是一個(gè)近似值

// 可以看到 sum()方法沒有任何加鎖的邏輯
public long sum() {
    Cell[] as = cells;
    Cell a;
    long sum = base;
    if (as != null) {
        for (int i = 0; i < as.length; ++i) {
            if ((a = as[i]) != null)
                sum += a.value;
        }
    }
    return sum;
}

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

相關(guān)文章

  • RabbitMQ消息丟失解決方案

    RabbitMQ消息丟失解決方案

    把這篇文章主要為大家介紹了如何保證RabbitMQ消息不丟失的解決方發(fā),分從從丟失的三種情況給大家介紹不同的解決方案,感興趣的小伙伴可以參考閱讀本文
    2023-07-07
  • Java數(shù)據(jù)結(jié)構(gòu)之鏈表相關(guān)知識(shí)總結(jié)

    Java數(shù)據(jù)結(jié)構(gòu)之鏈表相關(guān)知識(shí)總結(jié)

    今天給大家?guī)黻P(guān)于Java數(shù)據(jù)結(jié)構(gòu)的相關(guān)知識(shí),文章圍繞Java鏈表展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下
    2021-06-06
  • IntelliJ IDEA 如何徹底刪除項(xiàng)目的步驟

    IntelliJ IDEA 如何徹底刪除項(xiàng)目的步驟

    本篇文章主要介紹了IntelliJ IDEA 如何徹底刪除項(xiàng)目的步驟,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-11-11
  • MyBatis中if標(biāo)簽的基本使用

    MyBatis中if標(biāo)簽的基本使用

    本文介紹了MyBatis框架中的if標(biāo)簽的使用方法,包括動(dòng)態(tài)生成SQL語句、處理不同類型的參數(shù)和if標(biāo)簽進(jìn)行條件判斷,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-12-12
  • java實(shí)現(xiàn)圖片壓縮的思路與代碼

    java實(shí)現(xiàn)圖片壓縮的思路與代碼

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)圖片壓縮的思路與代碼,將較大的圖片按照指定的寬高,以寬為基準(zhǔn),或高為基準(zhǔn)按照等比例壓縮圖片,感興趣的小伙伴們可以參考一下
    2016-03-03
  • Java 中使用數(shù)組存儲(chǔ)和操作數(shù)據(jù)

    Java 中使用數(shù)組存儲(chǔ)和操作數(shù)據(jù)

    本文將介紹Java中常用的數(shù)組操作方法,通過詳細(xì)的示例和解釋,幫助讀者全面理解和掌握這些方法,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-09-09
  • JFormDesigner(IDEA)下載方法

    JFormDesigner(IDEA)下載方法

    JFormDesigner是一種Java Swing GUI設(shè)計(jì)工具,可快速創(chuàng)建用戶界面,支持多種布局管理器,如GridBagLayout、SpringLayout等,本文給大家介紹JFormDesigner(IDEA)下載方法,感興趣的朋友跟隨小編一起看看吧
    2023-12-12
  • Java基礎(chǔ)之引用相關(guān)知識(shí)總結(jié)

    Java基礎(chǔ)之引用相關(guān)知識(shí)總結(jié)

    今天聊聊Java的引用,大多數(shù)時(shí)候我們說引用都是強(qiáng)引用,只有在對象不使用的情況下才會(huì)釋放內(nèi)存,其實(shí)Java 內(nèi)存有四種不同的引用.一起看看吧,,需要的朋友可以參考下
    2021-05-05
  • 了解java中的session

    了解java中的session

    這篇文章主要介紹了了解java中的session的相關(guān)問題,什么是session,session怎么用等,具有一定參考價(jià)值,需要的朋友可以了解下。
    2017-11-11
  • 教你如何精準(zhǔn)統(tǒng)計(jì)出你的接口

    教你如何精準(zhǔn)統(tǒng)計(jì)出你的接口"QPS"

    今天小編就為大家分享一篇關(guān)于QPS的精準(zhǔn)計(jì)算方法,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧
    2021-08-08

最新評論

通山县| 苏尼特左旗| 阜平县| 敦化市| 江门市| 军事| 崇阳县| 武隆县| 柏乡县| 夏河县| 慈溪市| 大宁县| 威信县| 曲麻莱县| 肃宁县| 太白县| 陇西县| 藁城市| 铜梁县| 天祝| 遂宁市| 大连市| 衡阳市| 宣武区| 修水县| 永善县| 平潭县| 景宁| 长岛县| 雷山县| 滁州市| 南郑县| 海宁市| 东安县| 南华县| 镇赉县| 尤溪县| 邹城市| 平舆县| 汉源县| 汽车|