「Java」手把手理解CAS实现原理,jdk新特性面试题

本文详细解释了Java中的CAS(Compare and Swap)原理,指出其在多核环境下通过CPU原子指令保证线程安全。同时,文章讨论了高并发下的自旋阻塞和ABA问题,并提供了使用AtomicStampedReference解决ABA问题的示例。
摘要由CSDN通过智能技术生成

【+1】就是偏移量。

  • CAS的实现原理是什么?

CAS通过调用JNI的代码实现(JNI:Java Native Interface),允许java调用其他语言,

而【compareAndSwapXXX】系列的方法就是借助“C语言”来调用cpu底层指令实现的。

以常用的【Intel x86】平台来说,最终映射到cpu的指令为【cmpxchg】(compareAndChange),

这是一个原子指令,cpu执行此命令时,实现比较替换操作。

  • 那么问题来了,现在计算机动不动就上百核,【cmpxchg】怎么保证多核下的线程安全?

系统底层进行CAS操作时,会判断当前系统是否为多核系统,如果是,就给【总线】加锁,

只有一个线程对总线加锁成功, 加锁成功之后会执行CAS操作,也就是说CAS的原子性是平台级别的。

  • 那么问题又来了,CAS这么流批,就不会有什么问题么?

1》高并发下,其他线程会一直处于自旋阻塞状态

2》ABA问题(重要)

  • 什么是ABA问题呢?

CAS需要在操作值的时候,检查下值有没有发生变化,如果没有发生变化则更新,

但是可能会有这样一个情况,如果一个值原来是A,在CAS方法执行之前,被其他线程修改为了B,然后又修改回成A,

此时CAS方法执行之前,检查的时候发现它的值并没有发生变化,但实际却变化了,这就是【CAS的ABA】问题。

「Java」手把手理解CAS实现原理

  • 话不多说,我们这里用代码来模拟一下ABA问题:

public class CasABADemo1 {

private static AtomicInteger count = new AtomicInteger(0);

public static void main(String[] args) {

System.out.println("mainThread 当前count值为: " + count.get());

Thread mainThread = new Thread(() -> {

try {

int expectCount = count.get();

int updateCount = expectCount + 1;

System.out.println(“mainThread 期望值:” + expectCount + “, 修改值:” + updateCount);

Thread.sleep(2000);//休眠2000s ,释放cpu

boolean result = count.compareAndSet(expectCount, updateCount);

System.out.println("mainThread 修改count : " + result);

} catch (InterruptedException e) {

e.printStackTrace();

}

});

Thread otherThread = new Thread(() -> {

try {

Thread.sleep(20);//确保主线程先获取到cpu资源

} catch (InterruptedException e) {

e.printStackTrace();

}

count.incrementAndGet();

System.out.println(“其他线程先修改 count 为:” + count.get());

count.decrementAndGet();

System.out.println(“其他线程又修改 count 为:” + count.get());

});

mainThread.start();

otherThread.start();

}

}

结果:

mainThread 当前count值为: 0

mainThread 期望值:0, 修改值:1

其他线程先修改 count 为:1

其他线程又修改 count 为:0

mainThread 修改count : true

最后结果可以看出【mainThread】修改成功,但是【mainThread】获取到的【expectCount】虽然也是1,但已经不是曾经的【expectCount】。

  • 如何解决ABA问题呢?

解决ABA最简单的方案就是给值加一个版本号,每次值变化,都会修改他的版本号,

CAS操作时都去对比次版本号。

  • java中提供了一种版本号控制的方法,可以解决ABA问题:

public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp)

![「Java」手把手理解CAS实现原理](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8yMjk0MzQ0NS1mZjYzY2U1ODVhMTMyN2Y5?x-oss-process=image/form

《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》

【docs.qq.com/doc/DSmxTbFJ1cmN1R2dB】 完整内容开源分享

at,png)

  • 我们对上述代码改造一下,再看看结果:

public class CasABADemo2 {

private static AtomicStampedReference count = new AtomicStampedReference<>(0, 1);

public static void main(String[] args) {

System.out.println("mainThread 当前count值为: " + count.getReference() + “,版本号为:” + count.getStamp());

Thread mainThread = new Thread(() -> {

try {

int expectStamp = count.getStamp();

int updateStamp = expectStamp + 1;

int expectCount = count.getReference();

int updateCount = expectCount + 1;

System.out.println(“mainThread 期望值:” + expectCount + “, 修改值:” + updateCount);

Thread.sleep(2000);//休眠2000s ,释放cpu

boolean result = count.compareAndSet(expectCount, updateCount, expectStamp, updateStamp);

System.out.println("mainThread 修改count : " + result);

} catch (InterruptedException e) {

e.printStackTrace();

}

});

Thread otherThread = new Thread(() -> {

try {

Thread.sleep(20);//确保主线程先获取到cpu资源

} catch (InterruptedException e) {

e.printStackTrace();

}

count.compareAndSet(count.getReference(), count.getReference() + 1, count.getStamp(), count.getStamp() + 1);

System.out.println(“其他线程先修改 count 为:” + count.getReference() + " ,版本号:" + count.getStamp());

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值