天天看點

并發實戰----ABA的處理方法:AtomicStampedReference用法

       hello大家好,我是小卡,昨天給大家淺談了高并發下的CAS算法,以及更深層次的compareAndSwapObejct方法。在文章的最後提出了一個問題就是如何解決ABA的問題,今天花一點時間把這個問題給他家講一下。

       首先我們再來回顧一波為什麼會出現ABA?

       在多cpu的伺服器中可能會出現多線程操作這個容器,并同時執行CAS,因為cpu之前的任務排程排序不同,執行的速度也可能會不同,就可能會出現A還在執行compare方法的時候,B線程已經執行完swap操作,同時将記憶體值修改成了A線程的預期值,這時候計算機以為操作是對的。但是這确實有個錯誤的操作,這就是資料版本不一緻的問題。

并發實戰----ABA的處理方法:AtomicStampedReference用法

         昨天我們也提到過使用AtomicStampReference來解決和這個問題,今天我們就來看看什麼是AtomicStampReference。

public class AtomicStampedReference<V> {

    // 内部類,atomicStampedReference的本質就隻這個類
    private static class Pair<T> {
        // 對象引用
        final T reference;
        // 版本号
        final int stamp;
        private Pair(T reference, int stamp) {
            this.reference = reference;
            this.stamp = stamp;
        }
        static <T> Pair<T> of(T reference, int stamp) {
            return new Pair<T>(reference, stamp);
        }
    }

    private volatile Pair<V> pair;
           

        我們再來看一看他的核心方法:

public boolean compareAndSet(V   expectedReference,
                             V   newReference,
                             int expectedStamp,
                             int newStamp) {
    Pair<V> current = pair;
    //  标藍方法就是解決ABA問題的關鍵算法
    return
        // 原始值是否等于目前值 原始值 = 線程進入時的node值, 目前值 = 有可能被其他線程操作後的值
        expectedReference == current.reference &&
        // 原始版本是否和目前版本一緻
        expectedStamp == current.stamp &&
        // 新值(替換值) = 原始值 && 新版本号 = 舊版本号 (想當于沒有任何變化) 直接傳回true了
        ((newReference == current.reference &&
          newStamp == current.stamp) ||
        // 或者  新值、新版本号成功替換 原始值、原始版本号 傳回操作成功!
         casPair(current, Pair.of(newReference, newStamp)));
}
           

        他的最底層也是用我們Usafe包下的compareAndSwapObject()方方法。

private boolean casPair(Pair<V> cmp, Pair<V> val) {
    return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
}
           

上述代碼就是AtomicStampedReference的核心方法,接下來我們就要學會如何去用,怎麼樣用才能夠有效避免ABA。

package com.cdzg.shop.saleticket;

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

/**
 * @author xiefei
 * @version 1.0
 * @date 2020/5/14 14:40
 */
public class Test {
    public static void main(String[] args) {
        /**
         *  這裡需要重點注意的是因為我們解決ABA問題重點是關注 資料
         *  是以我們關注的點是在資料,而不是ConcurrentHashMap這個容器
         *  是以我們隻需要在将關注的資料用AtomicStampedReference修飾就好了
         *  我第一次使用AtomicStampedReference便出現過這個問題
         *  希望大家引以為戒!!!!
         */
        ConcurrentHashMap<String,AtomicStampedReference<String>> concurrentHashMap = new ConcurrentHashMap<>();
        AtomicStampedReference<String> stamp = new AtomicStampedReference<>("person",0);
        String mapKey = "original";
        concurrentHashMap.put(mapKey,stamp);

        // 起線程模拟高并發操作同一個node
        Thread mainThread = new Thread(() ->{
            System.out.println("----進入主線程----");
            // 原始資料
            AtomicStampedReference<String> reference = concurrentHashMap.get(mapKey);
            String oldStr = reference.getReference();
            int oldValue = reference.getStamp();
            // 模拟主線程網絡比較慢
            try{
                Thread.sleep(2000);
            }catch (InterruptedException e){
                System.out.println(e);
            }
            AtomicStampedReference<String> concurrentRef = concurrentHashMap.get(mapKey);
            System.out.println("目前資料:" + concurrentRef.getReference() + "--- 目前版本: " + concurrentRef.getStamp() );
            // 操作資料
            boolean mainFlag = reference.compareAndSet(oldStr, "god", oldValue, oldValue + 10);
            if(mainFlag) concurrentHashMap.put(mapKey,reference);
            System.out.println("----主線程操作結果----:" + mainFlag);
            System.out.println("----主線程結束----");
        },"主線程");

        Thread disturbThread = new Thread(() ->{
            System.out.println("----進入幹擾線程----");
            AtomicStampedReference<String> reference = concurrentHashMap.get(mapKey);
            String oldStr = reference.getReference();
            int oldValue = reference.getStamp();
            // 操作資料
            boolean disturbFlag = reference.compareAndSet(oldStr, "devil", oldValue, oldValue + 10);
            concurrentHashMap.put(mapKey,reference);
            System.out.println("----幹擾線程操作結果----:" + disturbFlag);
            System.out.println("----幹擾線程結束----");
        },"幹擾線程");

        mainThread.start();
        disturbThread.start();
    }
}
           

          提出操作結果,我們成功的利用AtomicStampedReference避免了ABA問題!

并發實戰----ABA的處理方法:AtomicStampedReference用法

         注:這裡有一點需要提醒大家,面試官可能會在面試的時候問到如何解決ABA的問題,我們在回答的時候要注意這個版本問題。首先這個版本使我們自定義的,其次這個版本的增幅也是我們自定義的 ,如上述代碼。因為我看見有很部落格寫的是内置版本,自動版本+1就很氣,那是錯的,不可取的!

        希望大家一鍵三連!!!愛你們!!!

        并發程式設計CAS連結:https://blog.csdn.net/qq_39941165/article/details/106092241

繼續閱讀