并发实战----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.****.net/qq_39941165/article/details/106092241