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

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

     

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

       在多cpu的服務器中可能會出現多線程操作這個容器,並同時執行CAS,因爲哥哥cpu之前的任務調度排序不同,執行的速度也可能會不同,就可能會出現A還在執行compare方法的時候,B線程已經執行完swap操作,同時將內存值修改成了A線程的預期值,這時候計算機以爲操作是對的。但是這確實有個錯誤的操作。
   

         昨天我們也提到過使用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的問題,我們在回答的時候要注意這個版本問題。首先這個版本使我們自定義的,其次這個版本的增幅也是我們自定義的 ,如上述代碼。因爲我看見有很博客寫的是內置版本,自動版本+1就很氣,那是錯的,不可取的!

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

        併發編程CAS鏈接:https://blog.csdn.net/qq_39941165/article/details/106092241

        

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