「Java」手把手理解 CAS 实现原理,学习 linux 编程
参数 var1:表示要操作的对象
参数 var2:表示要操作对象中属性地址的偏移量
参数 var4:表示需要修改数据的期望的值
参数 var5:表示需要修改为的新值
此处描述一下?偏移量?的概念?
这里的偏移量就像我们【new】一个对象,对象的地址就是【0x001】,那么 value 的地址就是【0x002 = 0x001 + 1】,
【+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】问题。
话不多说,我们这里用代码来模拟一下 ABA 问题:
public class CasABADemo1 {
private static Atomi
cInteger 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)
评论