//value对应的存储地址偏移量
private static final long valueOffset;
static {
try {
//使用反射及unsafe.objectFieldOffset拿到value字段的内存地址偏移量,这个值是固定不变的
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField(“value”));
} catch (Exception ex) { throw new Error(ex); }
}
//volatile修饰的共享变量
private volatile int value;
//…
}
上面的代码其实就是为了初始化内存值对应的内存地址偏移量valueOffset,方便后续执行CAS操作时使用。因为这个值一旦初始化,就不会更改,所以使用static final 修饰。
我们可以看到value使用了volatile修饰,其中也说了volatile的语义。
我们都知道如果进行value++操作,并发下是不安全的。上一篇中我们也通过例子证明了volatile只能保证可见性,不能保证原子性。因为value++本身不是原子操作,value++分了三步,先拿到value的值,进行+1,再赋值回value。
2.2、compareAndSwapXxx
我们先看一看AtomicInteger提供的CAS操作。
/**
- 原子地将value设置为update,如果valueOffset对应的值与expect相等时
- @param expect 期待值
- @param update 更新值
- @return 如果更新成功,返回true;在valueOffset对应的值与expect不相等时返回false
*/
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
我们已经知道CAS的原理,那来看看下面的测试。**你知道输出的结果是多少吗?**评论区给出你的答案吧。
public class AtomicIntegerTest {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger();
atomicInteger.compareAndSet(0, 1);
atomicInteger.compareAndSet(2, 1);
atomicInteger.compareAndSet(1, 3);
atomicInteger.compareAndSet(2, 4);
System.out.println(atomicInteger.get());
}
}
Unsafe提供了三个原子更新的方法。
关于Unsafe类,因为java不支持直接操作底层硬件资源,如分配内存等。如果你使用unsafe开辟的内存,是不被JVM垃圾回收管理,需要自己管理,容易造成内存泄漏等。
2.3、AtomicInteger的原子自增方法
我们上面说了,value++不是原子操作,不能在并发下使用。我们来看看AtomicInteger提供的原子++操作。
/**
- 原子地对value进行+1操作
- @return 返回更新后的值
*/
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
/**
- unsafe提供的方法
- var1 更改的目标对象
- var2 目标对象的共享字段对应的内存地址偏移量valueOffset
- var4 需要在原value上增加的值
- @return 返回未更新前的值
*/
public final int getAndAddInt(Object var1, long var2, int var4) {
//期待值
int var5;
do {
//获取valueOffset对应的value的值,支持volatile load
var5 = this.getIntVolatile(var1, var2);
//如果原子更新失败,则一直重试,直到成功。
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
**我们看到CAS只能原子的更新一个值,如果我们要原子更新多个值,CAS可以做到吗?**答案是可以的。
2.4、AtomicReference
如果要原子地更新多个值,就需要使用AtomicReference。其使用的是compareAndSwapObject方法。可以将多个值封装到一个对象中,原子地更换对象来实现原子更新多个值。
public class MultiValue {
private int value1;
private long value2;
private Integer value3;
public MultiValue(int value1, long value2, Integer value3) {
this.value1 = value1;
this.value2 = value2;
this.value3 = value3;
}
}
public class AtomicReferenceTest {
public static void main(String[] args) {
MultiValue multiValue1 = new MultiValue(1, 1, 1);
MultiValue multiValue2 = new MultiValue(2, 2, 2);
MultiValue multiValue3 = new MultiValue(3, 3, 3);
AtomicReference atomicReference = new AtomicReference<>();
//因为构造AtomicReference时,没有使用有参构造函数,所以value默认值是null
atomicReference.compareAndSet(null, multiValue1);
System.out.println(atomicReference.get());
atomicReference.compareAndSet(multiValue1, multiValue2);
System.out.println(atomicReference.get());
atomicReference.compareAndSet(multiValue2, multiValue3);
System.out.println(atomicReference.get());
}
}
//输出结果
//MultiValue{value1=1, value2=1, value3=1}
//MultiValue{value1=2, value2=2, value3=2}
//MultiValue{value1=3, value2=3, value3=3}
我们再看一看AtomicReference的compareAndSet方法。
注意:这里的比较都是使用==而非equals方法。所以最好封装的MultiValue不要提供set方法。
public final boolean compareAndSet(V expect, V update) {
return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}
2.5、CAS的ABA问题
假设你的账户上有100块钱,你要给女票转50块钱。
我们使用CAS进行原子更新账户余额。由于某种原因,你第一次点击转账出现错误,你以为没有发起转账请求,这时候你又点击了一次。系统开启了两个线程进行转账操作,第一个线程进行CAS比较,发现你的账户上预期是100块钱,实际也有100块钱,这时候转走了50,需要设置为100 - 50 = 50 元,这时账户余额为50
第一个线程操作成功了,第二个线程由于某种原因阻塞住了;这时候,你的家人又给你转了50块钱,并且转账成功。那你账户上现在又是100块钱;
**太巧了,第二个线程被唤醒了,发现你的账户是100块钱,跟预期的100是相等的,这时候又CAS为50。大兄弟,哭惨了,你算算,正确的场景你要有多少钱?**这就是CAS存在的ABA问题。
public class AtomicIntegerABA {
private static AtomicInteger atomicInteger = new AtomicInteger(100);
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
//线程1
executorService.execute(() -> {
System.out.println(Thread.currentThread().getName() + " - " + atomicInteger.get());
atomicInteger.compareAndSet(100, 50);
System.out.println(Thread.currentThread().getName() + " - " + atomicInteger.get());
});
//线程2
executorService.execute(() -> {
try {
TimeUnit.MILLISECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " - " + atomicInteger.get());
atomicInteger.compareAndSet(50, 100);
System.out.println(Thread.currentThread().getName() + " - " + atomicInteger.get());
});
//线程3
executorService.execute(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " - " + atomicInteger.get());
atomicInteger.compareAndSet(100, 50);
System.out.println(Thread.currentThread().getName() + " - " + atomicInteger.get());
});
executorService.shutdown();
}
}
//输出结果
//pool-1-thread-1 - 100
//pool-1-thread-1 - 50
//pool-1-thread-2 - 50
//pool-1-thread-2 - 100
//pool-1-thread-3 - 100
//pool-1-thread-3 - 50
大家心想,靠,这不是坑吗?那还用。。。。。。。。。。。。。。冷静,冷静。你能想到的问题,jdk都能想到。atomic包提供了一个AtomicStampedReference
2.6、AtomicStampedReference
看名字是不是跟AtomicReference很像啊,其实就是在AtomicReference上加上了一个版本号,每次操作都对版本号进行自增,那每次CAS不仅要比较value,还要比较stamp,当且仅当两者都相等,才能够进行更新。
public AtomicStampedReference(V initialRef, int initialStamp) {
pair = Pair.of(initialRef, initialStamp);
}
//定义了内部静态内部类Pair,将构造函数初始化的值与版本号构造一个Pair对象。
private static class Pair {
final T reference;
final int stamp;
private Pair(T reference, int stamp) {
this.reference = reference;
this.stamp = stamp;
}
static Pair of(T reference, int stamp) {
return new Pair(reference, stamp);
}
}
//所以我们之前的value就对应为现在的pair
private volatile Pair pair;
让我们来看一看它的CAS方法。
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair current = pair;
return
//只有在旧值与旧版本号都相同的时候才会更新为新值,新版本号
expectedReference == current.reference &&
expectedStamp == current.stamp &&
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。


既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Java)

最后:学习总结——MyBtis知识脑图(纯手绘xmind文档)
学完之后,若是想验收效果如何,其实最好的方法就是可自己去总结一下。比如我就会在学习完一个东西之后自己去手绘一份xmind文件的知识梳理大纲脑图,这样也可方便后续的复习,且都是自己的理解,相信随便瞟几眼就能迅速过完整个知识,脑补回来。下方即为我手绘的MyBtis知识脑图,由于是xmind文件,不好上传,所以小编将其以图片形式导出来传在此处,细节方面不是特别清晰。但可给感兴趣的朋友提供完整的MyBtis知识脑图原件(包括上方的面试解析xmind文档)

除此之外,前文所提及的Alibaba珍藏版mybatis手写文档以及一本小小的MyBatis源码分析文档——《MyBatis源码分析》等等相关的学习笔记文档,也皆可分享给认可的朋友!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
xmind文档)
[外链图片转存中…(img-eS290xbK-1713815756762)]
除此之外,前文所提及的Alibaba珍藏版mybatis手写文档以及一本小小的MyBatis源码分析文档——《MyBatis源码分析》等等相关的学习笔记文档,也皆可分享给认可的朋友!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
本文详细介绍了Java中的volatile关键字、CAS操作原理及其在AtomicInteger和AtomicReference中的应用,探讨了ABA问题以及AtomicStampedReference的解决方案。通过实例展示了如何使用这些技术保证并发环境下的数据一致性。
776

被折叠的 条评论
为什么被折叠?



