瞭解下AtomicReference與AtomicStampedReference

以下模擬一個情景,一家商鋪要做一個活動,對那些賬戶餘額小於10的用戶,充值20元禮金,促進消費。

使用AtomicReference來實現這個邏輯的demo:

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

public class AtomicReferenceTest {
    //設置一個初始值,爲6
    static AtomicReference<Integer> money = new AtomicReference(6);


    public static void main(String[] args) {
        //設置一個初始值,爲6
        money.set(6);
        //模擬後臺線程,這3個線程的作用就是無限循環找出賬戶金額小於10的用戶,然後對該用戶充值20元
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                {
                    while (true) {
                        while (true) {
                            Integer m = money.get();
                            if (m < 10) {
                                if (money.compareAndSet(m, m + 20)) {
                                    System.out.println("餘額小於十元,進行充值,充值後餘額=" + money.get() + "元");
                                    break;
                                }
                            } else {
                                break;
                            }
                        }
                    }
                }
            }).start();
        }
        //模擬用戶消費的線程,消費10次,每次只消費10元,金額不夠則給出提示
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                while (true) {
                    Integer m = money.get();
                    if (m > 10) {
                        System.out.println("賬戶餘額足夠,可以消費");
                        if (money.compareAndSet(m, m - 10)) {
                            System.out.println("消費成功,消費後餘額=" + money.get() + "元");
                            break;
                        }
                    } else {
                        System.out.println("餘額不足,餘額=" + money.get() + "元");
                        break;
                    }
                }
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

運行結果如下:
在這裏插入圖片描述
可以發現用戶賬戶只要金額小於10,後臺線程就會給用戶賬戶充值,如此反覆,這中邏輯肯定是不符合商家利益的,這也是CAS方式會出現的ABA問題。

接下來使用AtomicStampedReference來改造以上邏輯,如下:

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;

public class AtomicStampedReferenceTest {
    //設置一個初始值,爲6
    static AtomicStampedReference<Integer> money = new AtomicStampedReference(6, 0);

    public static void main(String[] args) {
        //模擬後臺線程,這3個線程的作用就是無限循環找出賬戶金額小於10的用戶,然後對該用戶充值20元
        for (int i = 0; i < 3; i++) {
            //取出stamp
            final int stamp = money.getStamp();
            new Thread(() -> {
                {
                    while (true) {
                        while (true) {
                            Integer m = money.getReference();
                            if (m < 10) {
                                if (money.compareAndSet(m, m + 20, stamp, stamp + 1)) {
                                    System.out.println("餘額小於十元,進行充值,充值後餘額=" + money.getReference() + "元");
                                    break;
                                }
                            } else {
                                break;
                            }
                        }
                    }
                }
            }).start();
        }
        //模擬用戶消費的線程,消費10次,每次只消費10元,金額不夠則給出提示
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                while (true) {
                    int stamp = money.getStamp();
                    Integer m = money.getReference();
                    if (m > 10) {
                        System.out.println("賬戶餘額足夠,可以消費");
                        if (money.compareAndSet(m, m - 10, stamp, stamp + 1)) {
                            System.out.println("消費成功,消費後餘額=" + money.getReference() + "元");
                            break;
                        }
                    } else {
                        System.out.println("餘額不足,餘額=" + money.getReference() + "元");
                        break;
                    }
                }
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

運行結果如下:
在這裏插入圖片描述
可以發現只充值了一次,不會再出現反覆充值的情況了。

總結

相比起AtomicReference,AtomicStampedReference除了維護了對象值,還維護了一個狀態標識位stamp,在更新對象值時,還需要同時更新stamp的值,二者合一纔是能否成功修改值的判斷規則,可以抽象的想象爲,使用了stamp屬性形成了一個對象值的修改流水歷史,這樣即使對象值被修改回原值,邏輯也不會再被運行,因爲stamp值肯定是不同的,那麼就不匹配規則了,這也是AtomicStampedReference能夠實現只充值了一次邏輯的關鍵所在,這也是對CAS操作的ABA問題的解決思路。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章