并发实战----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

        

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