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