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

JDK基于CAS實(shí)現(xiàn)原子類盤點(diǎn)解析

 更新時(shí)間:2022年12月06日 10:52:21   作者:JAVA旭陽  
這篇文章主要為大家介紹了JDK基于CAS實(shí)現(xiàn)原子類盤點(diǎn)解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

JDK中提供了一系列的基于CAS實(shí)現(xiàn)的原子類,CAS 的全稱是Compare-And-Swap,底層是lock cmpxchg指令,可以在單核和多核 CPU 下都能夠保證比較交換的原子性。所以說,這些原子類都是線程安全的,而且是無鎖并發(fā),線程不會(huì)頻繁上下文切換,所以在某些場景下性能是優(yōu)于加鎖。

本文就盤點(diǎn)一下JDK中的原子類,方便我們后續(xù)拿來使用。

基礎(chǔ)原子類

  • AtomicInteger:Integer整數(shù)類型的原子操作類
  • AtomicBoolean:Boolean類型的原子操作類
  • AtomicLong:Long類型的原子操作類

這邊以AtomicInteger講解下它的API和用法。

構(gòu)造方法:

  • public AtomicInteger():初始化一個(gè)默認(rèn)值為 0 的原子型 Integer
  • public AtomicInteger(int initialValue):初始化一個(gè)指定值的原子型 Integer

常用API:

  • public final int get(): 獲取 AtomicInteger 的值
  • public final int getAndIncrement(): 以原子方式將當(dāng)前值加 1,返回的是自增前的值
  • public final int incrementAndGet():以原子方式將當(dāng)前值加 1,返回的是自增后的值
  • public final int getAndSet(int value):以原子方式設(shè)置為 newValue 的值,返回舊值
  • public final int addAndGet(int data):以原子方式將輸入的數(shù)值與實(shí)例中的值相加并返回

使用:

  • 結(jié)果1000,大致說明并發(fā)情況下保證了線程安全

原理分析:

整體實(shí)現(xiàn)思路: 自旋(循環(huán)) + CAS算法

  • 當(dāng)舊的預(yù)期值 A == 內(nèi)存值 V 此時(shí)可以修改,將 V 改為 B
  • 當(dāng)舊的預(yù)期值 A != 內(nèi)存值 V 此時(shí)不能修改,并重新獲取現(xiàn)在的最新值,重新獲取的動(dòng)作就是自旋
public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
 }
  • valueOffset:偏移量表示該變量值相對于當(dāng)前對象地址的偏移,Unsafe 就是根據(jù)內(nèi)存偏移地址獲取數(shù)據(jù)

  • 從主內(nèi)存中拷貝到工作內(nèi)存中的值(每次都要從主內(nèi)存拿到最新的值到本地內(nèi)存),然后執(zhí)行 compareAndSwapInt() 再和主內(nèi)存的值進(jìn)行比較,假設(shè)方法返回 false,那么就一直執(zhí)行 while 方法,直到期望的值和真實(shí)值一樣,修改數(shù)據(jù)。

  • 原子類AtomicIntegervalue屬性是volatile類型,保證了多線程之間的內(nèi)存可見性,避免線程從工作緩存中獲取失效的變量。

原子引用

原子引用主要是對對象的原子操作,原子引用類分為AtomicReferenceAtomicStampedReference、AtomicMarkableReference。它們之間有什么區(qū)別呢?

  • AtomicReference類

普通的原子類對象

public class AtomicReferenceDemo {
    public static void main(String[] args) {
        User user1 = new User("旭陽");
        // 創(chuàng)建原子引用包裝類
        AtomicReference<User> atomicReference = new AtomicReference<>(user1);
        while (true) {
            User user2 = new User("alvin");
            // 比較并交換
            if (atomicReference.compareAndSet(user1, user2)) {
                break;
            }
        }
        System.out.println(atomicReference.get());
    }
}
@Data
@AllArgsConstructor
@ToString
class User {
    private String name;
}
  • 調(diào)用compareAndSet()方法進(jìn)行比較替換對象

ABA問題

但是如果使用AtomicReference類,會(huì)有一個(gè)ABA問題。什么意思呢?就是一個(gè)線程將共享變量從A改成B, 后面又改回A, 這是,另外一個(gè)線程就無法感知這個(gè)變化過程,就傻傻的比較,就以為沒有變化,還是一開始的A,就替換了。 實(shí)際的確存在這樣只要共享變量發(fā)生過變化,就要CAS失敗,有什么辦法呢?

  • AtomicStampedReference類

帶版本號的原子類對象

@Slf4j(topic = "a.AtomicStampedReferenceTest")
public class AtomicStampedReferenceTest {
    // 構(gòu)造AtomicStampedReference
    static AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);
    public static void main(String[] args) throws InterruptedException {
        log.debug("main start...");
        // 獲取值 A
        String prev = ref.getReference();
        // 獲取版本號
        int stamp = ref.getStamp();
        log.debug("版本 {}", stamp);
        // 如果中間有其它線程干擾,發(fā)生了 ABA 現(xiàn)象
        other();
        Thread.sleep(1000);
        // 嘗試改為 C
        log.debug("change A->C {}", ref.compareAndSet(prev, "C", stamp, stamp + 1));
    }
    private static void other() throws InterruptedException {
        new Thread(() -> {
            log.debug("change A->B {}", ref.compareAndSet(ref.getReference(), "B",
                    ref.getStamp(), ref.getStamp() + 1));
            log.debug("更新版本為 {}", ref.getStamp());
        }, "t1").start();
        Thread.sleep(500);
        new Thread(() -> {
            log.debug("change B->A {}", ref.compareAndSet(ref.getReference(), "A",
                    ref.getStamp(), ref.getStamp() + 1));
            log.debug("更新版本為 {}", ref.getStamp());
        }, "t2").start();
    }
}

  • 雖然對象的值變回了A,但是由于版本變了,所以主線程CAS失敗
  • AtomicMarkableReference 類

其實(shí)有時(shí)候并不關(guān)心共享變量修改了幾次,而是只要標(biāo)記下是否發(fā)生過更改,是否加個(gè)標(biāo)記即可,所以就有了AtomicMarkableReference類。

@Slf4j(topic = "c.AtomicMarkableReferenceTest")
public class AtomicMarkableReferenceTest {
    // 構(gòu)造 AtomicMarkableReference, 初始標(biāo)記為false
    static AtomicMarkableReference<String> ref = new AtomicMarkableReference<>("A", false);
    public static void main(String[] args) throws InterruptedException {
        log.debug("main start...");
        other();
        Thread.sleep(1000);
        // 看看是否發(fā)生了變化
        log.debug("change {}", ref.isMarked());
    }
    private static void other() throws InterruptedException {
        new Thread(() -> {
            log.debug("change A->B {}", ref.compareAndSet(ref.getReference(), "B",
                    false, true));
        }, "t1").start();
        Thread.sleep(500);
        new Thread(() -> {
            log.debug("change B->A {}", ref.compareAndSet(ref.getReference(), "A",
                    true, true));
        }, "t2").start();
    }
}

  • 通過調(diào)用isMarked()方法查看是否發(fā)生變化。

原子數(shù)組

  • AtomicIntegerArray: Integer類型的原子數(shù)組
  • AtomicLongArray:Long類型的原子數(shù)組
  • AtomicReferenceArray:引用類型的原子數(shù)組

直接上例子

public class AtomicIntegerArrayTest {
    public static void main(String[] args) throws Exception{
        AtomicIntegerArray array = new AtomicIntegerArray(10);
        Thread t1 = new Thread(()->{
            int index;
            for(int i=1; i<100000; i++) {
                index = i%10; //范圍0~9
                array.incrementAndGet(index);
            }
        });
        Thread t2 = new Thread(()->{
            int index;
            for(int i=1; i<100000; i++) {
                index = i%10; //范圍0~9
                array.decrementAndGet(index);
            }
        });
        t1.start();
        t2.start();
        Thread.sleep(5 * 1000);
        System.out.println(array.toString());
    }
}

  • 兩個(gè)線程同時(shí)對數(shù)組對象進(jìn)行加和減的操作,最終結(jié)果都是0,說明線程安全。

原子字段更新器

AtomicReferenceFieldUpdater

AtomicIntegerFieldUpdater

AtomicLongFieldUpdater

利用字段更新器,可以針對對象的某個(gè)域(Field)進(jìn)行原子操作,只能配合 volatile 修飾的字段使用,否則會(huì)出現(xiàn)異常。

@Data
public class AtomicReferenceFieldUpdaterTest {
    private volatile int age = 10;
    private int age2;
    public static void main(String[] args) {
        AtomicIntegerFieldUpdater integerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(AtomicReferenceFieldUpdaterTest.class, "age");
        AtomicReferenceFieldUpdaterTest ref = new AtomicReferenceFieldUpdaterTest();
        // 對volatile 的age字段+1
        integerFieldUpdater.getAndIncrement(ref);
        System.out.println(ref.getAge());
        // 修改 非volatile的age2
        integerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(AtomicReferenceFieldUpdaterTest.class, "age2");
        integerFieldUpdater.getAndIncrement(ref);
    }
}

  • 原子字段更新器只能更新volatile字段,它可以保證可見性,但是無法保證原子性。

原子累加器

原子累加器主要是用來做累加的,相關(guān)的類有LongAdder、DoubleAdder、LongAccumulator、DoubleAccumulator。

LongAdder是jdk1.8中引入的,它的性能要比AtomicLong方式好。

LongAddr 類是 LongAccumulator 類的一個(gè)特例,只是 LongAccumulator 提供了更強(qiáng)大的功能,可以自定義累加規(guī)則,當(dāng)accumulatorFunction 為 null 時(shí)就等價(jià)于 LongAddr。

這邊做個(gè)性能的對比例子。

public class LongAdderTest {
    public static void main(String[] args) {
        System.out.println("LongAdder ...........");
        for (int i = 0; i < 5; i++) {
            addFunc(() -> new LongAdder(), adder -> adder.increment());
        }
        System.out.println("AtomicLong ...........");
        for (int i = 0; i < 5; i++) {
            addFunc(() -> new AtomicLong(), adder -> adder.getAndIncrement());
        }
    }
    private static <T> void addFunc(Supplier<T> adderSupplier, Consumer<T> action) {
        T adder = adderSupplier.get();
        long start = System.nanoTime();
        List<Thread> ts = new ArrayList<>();
        // 40個(gè)線程,每人累加 50 萬
        for (int i = 0; i < 40; i++) {
            ts.add(new Thread(() -> {
                for (int j = 0; j < 500000; j++) {
                    action.accept(adder);
                }
            }));
        }
        ts.forEach(t -> t.start());
        ts.forEach(t -> {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        long end = System.nanoTime();
        System.out.println(adder + " cost:" + (end - start)/1000_000);
    }
}

主要是由于LongAdder會(huì)設(shè)置多個(gè)累加單元,Therad-0 累加 Cell[0],而 Thread-1 累加Cell[1]... 最后將結(jié)果匯總。這樣它們在累加時(shí)操作的不同的 Cell 變量,因此減少了 CAS 重試失敗,從而提高性能。

總結(jié)

本文總結(jié)了JDK中提供的各種原子類,包括基礎(chǔ)原子類、原子引用類、原子數(shù)組類、原子字段更新器和原子累加器等。有時(shí)候,使用這些原子類的性能是比加鎖要高的,特別是在讀多寫少的場景下。但是,不知道大家發(fā)現(xiàn)沒有,所有的原子類操作對于一個(gè)共享變量執(zhí)行操作是原子的,如果對于多個(gè)共享變量操作時(shí),循環(huán) CAS 就無法保證操作的原子性,還是老老實(shí)實(shí)加鎖吧,更多關(guān)于JDK CAS原子類的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 帶你快速搞定java多線程

    帶你快速搞定java多線程

    這篇文章主要介紹了java多線程編程實(shí)例,分享了幾則多線程的實(shí)例代碼,具有一定參考價(jià)值,加深多線程編程的理解還是很有幫助的,需要的朋友可以參考下
    2021-07-07
  • Mac M1 Java 開發(fā)環(huán)境配置詳解

    Mac M1 Java 開發(fā)環(huán)境配置詳解

    這篇文章主要介紹了Mac M1 Java 開發(fā)環(huán)境配置詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-03-03
  • Netty分布式ByteBuf緩沖區(qū)分配器源碼解析

    Netty分布式ByteBuf緩沖區(qū)分配器源碼解析

    這篇文章主要為大家介紹了Netty分布式ByteBuf緩沖區(qū)分配器源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步
    2022-03-03
  • 里氏代換原則_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    里氏代換原則_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    這篇文章主要為大家詳細(xì)介紹了里氏代換原則的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-08-08
  • Shiro在springboot中快速實(shí)現(xiàn)方法

    Shiro在springboot中快速實(shí)現(xiàn)方法

    Apache Shiro是一個(gè)Java的安全(權(quán)限)框架,可以容易的開發(fā)出足夠好的應(yīng)用,既可以在JavaEE中使用,也可以在JavaSE中使用,這篇文章主要介紹了Shiro在springboot中快速實(shí)現(xiàn),需要的朋友可以參考下
    2023-02-02
  • springboot 排除redis的自動(dòng)配置操作

    springboot 排除redis的自動(dòng)配置操作

    這篇文章主要介紹了springboot 排除redis的自動(dòng)配置操作,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • Java pom.xml parent引用報(bào)錯(cuò)問題解決方案

    Java pom.xml parent引用報(bào)錯(cuò)問題解決方案

    這篇文章主要介紹了Java pom.xml parent引用報(bào)錯(cuò)問題解決方案,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-08-08
  • Java線程間的通信方式詳解

    Java線程間的通信方式詳解

    這篇文章主要為大家詳細(xì)介紹了Java線程間的通信方式,以代碼結(jié)合文字的方式來討論線程間的通信,感興趣的朋友可以參考一下
    2016-05-05
  • SpringBoot集成JWT實(shí)現(xiàn)登陸驗(yàn)證的方法詳解

    SpringBoot集成JWT實(shí)現(xiàn)登陸驗(yàn)證的方法詳解

    JSON?Web?Token(JWT)是一個(gè)開放的標(biāo)準(zhǔn)(RFC?7519),它定義了一個(gè)緊湊且自包含的方式,用于在各方之間以JSON對象安全地傳輸信息。本文將利用SpringBoot集成JWT實(shí)現(xiàn)登陸驗(yàn)證,感興趣的可以了解一下
    2022-05-05
  • springBoot?之spring.factories擴(kuò)展機(jī)制示例解析

    springBoot?之spring.factories擴(kuò)展機(jī)制示例解析

    這篇文章主要為大家介紹了springBoot?之spring.factories擴(kuò)展機(jī)制示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-04-04

最新評論

灌南县| 耒阳市| 澄迈县| 米脂县| 大邑县| 拉萨市| 资阳市| 华宁县| 哈尔滨市| 惠州市| 博野县| 滦南县| 遂平县| 当涂县| 石城县| 普宁市| 应城市| 临泽县| 芜湖市| 青河县| 芒康县| 伊春市| 平远县| 乌鲁木齐市| 湛江市| 海淀区| 盘山县| 招远市| 屯留县| 怀宁县| 哈尔滨市| 浦城县| 明水县| 沙湾县| 盈江县| 兴化市| 清丰县| 筠连县| 遵义市| 肇庆市| 灌云县|