成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

一文讀懂 Java 中的原子類

jas0n / 1145人閱讀

摘要:一無鎖方案并發(fā)包中的原子類都是基于無鎖方案實(shí)現(xiàn)的,相較于傳統(tǒng)的互斥鎖,無鎖并沒有加鎖解鎖線程切換的消耗,因此無鎖解決方案的性能更好,同時(shí)無鎖還能夠保證線程安全。線程首先讀取的值并加,如果此時(shí)有另一個(gè)線程更新了,則期望值和不相等,更新失敗。

一、無鎖方案

Java 并發(fā)包中的原子類都是基于無鎖方案實(shí)現(xiàn)的,相較于傳統(tǒng)的互斥鎖,無鎖并沒有加鎖、解鎖、線程切換的消耗,因此無鎖解決方案的性能更好,同時(shí)無鎖還能夠保證線程安全。

1. 無鎖方案的實(shí)現(xiàn)原理

無鎖主要依賴 CAS(Compare And Swap) ,即比較并交換,CAS 是一條 CPU 指令,其本身是能夠保證原子性的。CAS 中有三個(gè)參數(shù):

共享變量的內(nèi)存地址 A

用于比較的值 B

共享變量的新值 C

public class SimpleCAS {

    private int value;

    public synchronized int cas(int expectVal, int newVal){
        int curVal = value;
        if (expectVal == curVal){
            value = newVal;
        }
        return curVal;
    }
}

上面的代碼展示了 CAS 的簡(jiǎn)單實(shí)現(xiàn),從內(nèi)存中讀出當(dāng)前 value 的值,并且需要判斷,期望值 expectVal == curVal 的時(shí)候,才會(huì)將 value 更新為新值。

仍然以上面的代碼,來實(shí)現(xiàn)一個(gè)簡(jiǎn)單的,基于 CAS 的線程安全的 value+1 方法。這里的 cas 方法僅用于幫助理解,所以執(zhí)行結(jié)果可能有出入。

public class SimpleCAS {

    private volatile int value;

    public void addValue(){
        int newVal = value + 1;
        while (value != cas(value, newVal)){
            newVal = value + 1;
        }
    }
    
    private synchronized int cas(int expectVal, int newVal){
        int curVal = value;
        if (expectVal == curVal){
            value = newVal;
        }
        return curVal;
    }
}

線程首先讀取 value 的值并加 1,如果此時(shí)有另一個(gè)線程更新了 value,則期望值和 value 不相等,更新失敗。更新失敗后,循環(huán)嘗試,重新讀取 value 的值,直到更新成功退出循環(huán)。

2. ABA 問題

無鎖的實(shí)現(xiàn)方案中需要注意的一個(gè)問題便是 ABA 問題。

例如上面的代碼,value 的初始值為 0,線程 t1 取到了 value 的值,并將其更新為 1,然后線程又將 value 更新為 0。

假如這個(gè)過程中有另外一個(gè)線程 t2,和 t1 同時(shí)取初始值為 0 的 value,t2 在 t1 執(zhí)行完后更新 value,這個(gè)時(shí)候 value 雖然還是為 0,但已經(jīng)被 t1 修改過了。

大多數(shù)情況下,我們并不需要關(guān)心 ABA 問題,例如數(shù)值型數(shù)據(jù)的加減,但是對(duì)象類型的數(shù)據(jù)遇到了 ABA 問題的話,可能前后的屬性已經(jīng)發(fā)生了變化,所以需要解決。

解決的辦法也很簡(jiǎn)單,給對(duì)象類型的數(shù)據(jù)加上一個(gè)版本號(hào)即可,每更新一次,版本號(hào)加 1,這樣即使對(duì)象數(shù)據(jù)從 A 變成 B 后 又變成 A,但是版本號(hào)是遞增的,就可以分辨出對(duì)象還是被修改過的。

二、原子類 1. 原子化基本數(shù)據(jù)類型

有三個(gè)實(shí)現(xiàn)類:AtomicBoolean、AtomicInteger、AtomicLong

常用的方法如下,以 AtomicInteger 為例,其他的類似:

AtomicInteger i = new AtomicInteger(0);

i.getAndSet(int newValue);//獲取當(dāng)前值并設(shè)置新值

i.getAndIncrement();//相當(dāng)于 i ++
i.incrementAndGet();//相當(dāng)于 ++ i

i.getAndDecrement();//相當(dāng)于 i --
i.decrementAndGet();//相當(dāng)于 -- i

i.addAndGet(int delta);//相當(dāng)于 i + delta,并返回添加后的值
i.getAndAdd(int delta);//相當(dāng)于 i + delta,并返回添加前的值

i.compareAndSet(int expect, int update);//CAS 操作,返回 boolean值,表示是否更新成功

i.getAndUpdate(update -> 10);//通過函數(shù)更新值
i.updateAndGet(update -> 10);//類似上面
2. 原子化對(duì)象引用類型

實(shí)現(xiàn)類分別是:AtomicReference、AtomicStampedReference、AtomicMarkableReference,其中后兩個(gè)可以實(shí)現(xiàn)了解決 ABA 問題的方案。

AtomicReference 常用的方法如下:

//假設(shè)有一個(gè)叫做 Order 的類
AtomicReference orderReference = new AtomicReference<>();

orderReference.getAndSet(Order newValue);//獲取并設(shè)置

orderReference.set(Order order);//設(shè)置值
Order order1 = orderReference.get();//獲取對(duì)象

orderReference.compareAndSet(Order expect, Order update);//比較交換

orderReference.getAndUpdate();//通過函數(shù)更新值
orderReference.updateAndGet();

AtomicStampedReference 需要傳入初始值和初始 stamp,其中 stamp 相當(dāng)于對(duì)象的版本號(hào)(用來解決 ABA 問題),使用示例如下:

AtomicStampedReference reference = new AtomicStampedReference<>("roseduan", 0);

//嘗試修改stamp的值
boolean b = reference.attemptStamp(reference.getReference(), 10);

//獲取值
String str = reference.getReference();

//獲取stamp
int stamp = reference.getStamp();

//重新設(shè)置值和stamp
reference.set("I am not roseduan", 20);

//比較交換
boolean b1 = reference.compareAndSet("roseduan", "jack", 20, 0);

AtomicMarkableReference 使用一個(gè) mark 標(biāo)記(boolean 類型) 代替了 AtomicStampedReference 中的 stamp,用這種更簡(jiǎn)單的方式來解決 ABA 問題。使用的方式和上面的類似,只是將方法中的 stamp 變?yōu)?boolean 類型的值即可。

3. 原子化數(shù)組類型

實(shí)現(xiàn)類有三個(gè):

AtomicIntegerArray:原子化的整型數(shù)組

AtomicLongArray:原子化長(zhǎng)整型數(shù)組

AtomicReferenceArray:原子化對(duì)象引用數(shù)組

使用和原子化基本類型都是差不多的,只是需要在方法中加上數(shù)組下標(biāo)即可。

4. 原子化對(duì)象屬性更新器

也有三個(gè)實(shí)現(xiàn)類:

AtomicIntegerFieldUpdater:更新對(duì)象的整型屬性

AtomicLongFieldUpdater:更新對(duì)象的長(zhǎng)整型屬性

AtomicReferenceFieldUpdater:更新對(duì)象的引用型屬性

這三個(gè)類都是利用 Java 的反射機(jī)制實(shí)現(xiàn)的,并且為了保證原子性,要求被更新的對(duì)象的屬性必須是 volatile 類型的。使用示例如下:

@Data
@Builder
public class User {

    private volatile int age;

    private volatile long number;

    private volatile String name;

    public static void main(String[] args) {
        User user = User.builder().age(22).number(15553663L).name("roseduan").build();

        //更新age屬性的值
        AtomicIntegerFieldUpdater integerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");
        integerFieldUpdater.set(user, 25);

        //更新number屬性的值
        AtomicLongFieldUpdater longFieldUpdater = AtomicLongFieldUpdater.newUpdater(User.class, "number");
        longFieldUpdater.set(user, 1000101L);

        //更新對(duì)象類型的屬性的值
        AtomicReferenceFieldUpdater referenceFieldUpdater = AtomicReferenceFieldUpdater.newUpdater(User.class, String.class, "name");
        referenceFieldUpdater.set(user, "I am not roseduan");

        System.out.println(user.toString());
    }
}

程序中創(chuàng)建了一個(gè) User 類,有三個(gè)屬性 age、number、name 分別對(duì)應(yīng)整型、長(zhǎng)整型、引用類型。然后使用對(duì)象屬性更新器進(jìn)行屬性值的更新,更新器的其他方法的使用和前面說到的幾種原子化類型類似。

5. 原子化累加器

實(shí)現(xiàn)類有四個(gè):

DoubleAdder

DoubleAccumulator

LongAdder

LongAccumulator

這幾個(gè)類的功能有限,僅用來執(zhí)行累加操作,但是速度非???。下面介紹 DoubleAdder 和 DoubleAccumulator 的用法,其余兩個(gè)類似。

//DoubleAccumulator使用示例
DoubleAccumulator a = new DoubleAccumulator(Double::sum, 0);//設(shè)初始值為0
//累加
a.accumulate(1);
a.accumulate(2);
a.accumulate(3);
a.accumulate(4);

System.out.println(a.get());//輸出10

//DoubleAdder使用示例
DoubleAdder adder = new DoubleAdder();
adder.add(1);
adder.add(2);
adder.add(3);
adder.add(4);
adder.add(5);

System.out.println(adder.intValue());//輸出15

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/74612.html

相關(guān)文章

  • 從源碼入手,一文帶你讀懂Spring AOP面向切面編程

    摘要:,,面向切面編程。,切點(diǎn),切面匹配連接點(diǎn)的點(diǎn),一般與切點(diǎn)表達(dá)式相關(guān),就是切面如何切點(diǎn)。例子中,注解就是切點(diǎn)表達(dá)式,匹配對(duì)應(yīng)的連接點(diǎn),通知,指在切面的某個(gè)特定的連接點(diǎn)上執(zhí)行的動(dòng)作。,織入,將作用在的過程。因?yàn)樵创a都是英文寫的。 之前《零基礎(chǔ)帶你看Spring源碼——IOC控制反轉(zhuǎn)》詳細(xì)講了Spring容器的初始化和加載的原理,后面《你真的完全了解Java動(dòng)態(tài)代理嗎?看這篇就夠了》介紹了下...

    wawor4827 評(píng)論0 收藏0
  • 一文讀懂微服務(wù)架構(gòu)的重構(gòu)策略

    摘要:相反,它由單體中的適配器和使用一個(gè)或多個(gè)進(jìn)程間通信機(jī)制的服務(wù)組成。因?yàn)槲⒎?wù)架構(gòu)的本質(zhì)是一組圍繞業(yè)務(wù)功能組織的松耦合服務(wù)。如果你嘗試將此類功能實(shí)現(xiàn)為服務(wù),則通常會(huì)發(fā)現(xiàn),由于過多的進(jìn)程間通信而導(dǎo)致性能下降。這是快速展示微服務(wù)架構(gòu)價(jià)值的好方法。你很有可能正在處理大型復(fù)雜的單體應(yīng)用程序,每天開發(fā)和部署應(yīng)用程序的經(jīng)歷都很緩慢而且很痛苦。微服務(wù)看起來非常適合你的應(yīng)用程序,但它也更像是一項(xiàng)遙不可及的必殺...

    jaysun 評(píng)論0 收藏0
  • IaaS的演進(jìn)!一文讀懂裸金屬和容器即服務(wù)

    摘要:英特爾機(jī)架規(guī)模設(shè)計(jì)則能實(shí)現(xiàn)以機(jī)架為單位的軟硬件解耦,為裸金屬即服務(wù)提供容量更大的資源池,并可通過開放的和協(xié)議如和,高效發(fā)掘管理和調(diào)配這些資源。 江湖上,一直流傳著得IaaS(基礎(chǔ)設(shè)施即服務(wù)),得公有云天下的說法。想握緊IaaS這柄云端殺手锏,?大熱的裸金屬即服務(wù)和容器即服務(wù),還不了解一下??它們?yōu)槭裁慈绱耸苋?..

    MadPecker 評(píng)論0 收藏0
  • 一文讀懂Java線程狀態(tài)轉(zhuǎn)換

    摘要:前言本文描述線程線程狀態(tài)及狀態(tài)轉(zhuǎn)換,不會(huì)涉及過多理論,主要以代碼示例說明線程狀態(tài)如何轉(zhuǎn)換。被終止線程執(zhí)行完畢正常結(jié)束或執(zhí)行過程中因未捕獲異常意外終止都會(huì)是線程進(jìn)入被終止?fàn)顟B(tài)。線程執(zhí)行完畢打印狀態(tài)。 前言 本文描述Java線程線程狀態(tài)及狀態(tài)轉(zhuǎn)換,不會(huì)涉及過多理論,主要以代碼示例說明線程狀態(tài)如何轉(zhuǎn)換。 基礎(chǔ)知識(shí) 1. 線程狀態(tài) 線程可以有6種狀態(tài): New(新建) Runnable(可運(yùn)...

    summerpxy 評(píng)論0 收藏0
  • 一文讀懂鏈路追蹤

    摘要:鏈路追蹤鏈路追蹤一詞是在年提出的,當(dāng)時(shí)谷歌發(fā)布了一篇論文,介紹了谷歌自研的分布式鏈路追蹤的實(shí)現(xiàn)原理,還介紹了他們是怎么低成本實(shí)現(xiàn)對(duì)應(yīng)用透明的。感興趣的同學(xué)可以去深入了解一下鏈路追蹤,希望本文對(duì)你有所幫助。 showImg(https://upload-images.jianshu.io/upload_images/13711841-f54b415cc8d07fdc?imageMogr2...

    JerryWangSAP 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<