大家好,我是栗子为
又有好长时间没有和大家见面了,最近小为也是忙着入职,所以在学习上有了一点点懈怠,培训的日子每天的安排真是满满当当,小为也是心有余而力不足。这不,一有了空闲时间,小为决定接着上次打开的话题继续聊下去,Java并发编程中的那些事,这对我即将要处理的工作也有一定的帮助
小为今天想和大家说的是面试中常会被问到的CAS原理,我想这个知识点大家肯定不陌生,甚至还能说出它的全称Compare And Swap。这是一点没错,希望大家跟着小为学完后,不仅仅是讲出它的全称,更是让大家在面试中轻松地讲清楚它的原理
我们都知道可以利用原子类来处理一些多线程的场景,保证数据的一致性,原子类就是利用到了CAS这样的机制,那我们思考一个问题,在CAS之前,我们是如何不利用原子类来保证线程安全的呢?
01
—
不使用原子类保证线程安全
public class WithoutAtomic {
volatile int number = 0;
public int getNumber() {
return number;
}
public synchronized void setNumber() {
number++;
}
}
可以通过对写操作加锁来保证线程安全,对于读操作不用加锁,volatile关键字能保证每次都从主内存中读取最新的值
使用原子类保证线程安全
public class WithAtomic {
AtomicInteger atomicInteger = new AtomicInteger();
public int getAtomicInteger() {
return atomicInteger.get();
}
public void setAtomicInteger() {
atomicInteger.getAndIncrement();
}
}
这是不是有点像乐观锁的思想,没错
接着就引入我们今天要聊的内容-CAS
02
—
什么是CAS?
Compare and swap,翻译过来是比较并交换
它包含有三个操作数:
-
内存位置V -
预期原值A -
更新值B
我们在执行CAS操作的时候,会先将内存位置的值与预期原值进行比较:
-
如果匹配,那处理器会自动将该位置值更新为新值 -
如果不匹配,处理器不做任何操作或者重试(自旋),多个线程同时执行CAS操作只有一个会成功
我们来看看硬件层面是怎么实现CAS的?
CAS是一条CPU的原子指令(cmpxchg指令),不会造成所谓的数据不一致问题,Unsafe提供的CAS方法(如compareAndSwapXXX)底层实现即为CPU指令cmpxchg
执行cmpxchg指令的时候,首先会判断系统是否为多核系统,如果是,就会给总线加锁,只有一个线程会对总线加锁成功,加锁成功之后会执行CAS操作,也就是说,CAS原子性实际上是CPU实现独占,相比synchronized重量级锁,排他时间要短很多,所以在多线程情况下性能会比较好
看一个常见的🌰
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(5);
System.out.println(atomicInteger.compareAndSet(5, 2022) + "t" + atomicInteger.get());
}
compareAndSet
方法的底层逻辑如下
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
其实是调用了unsafe
的compareAndSwapInt
方法,unsafe类有如下native方法
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
参数说明
-
var1:表示要操作的对象 -
var2:表示要操作对象中属性地址的偏移量 -
var4:表示需要修改数据的期望的值 -
var5/var6:表示需要修改为的新值
03
—
Unsafe类
在上面我们提到了Unsafe类,CAS是通过实现Unsafe类的compareAndSwapInt
方法来实现的,那么Unsafe类到底是什么呢?
Unsafe类是CAS的核心类,由于Java不能直接访问底层系统,需要通过本地native方法来访问,基于该类可以操作特定内存的数据。
注:Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务
04
—
AtomicInteger实现原理
1.通过Unsafe类的静态方法,得到unsafe实例
private static final Unsafe unsafe = Unsafe.getUnsafe();
2.Unsafe根据内存偏移地址获取数据
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
变量valueOffset表示该变量值在内存中的偏移地址
3.volatile保证多线程之间内存可见性
private volatile int value;
05
—
AtomicInteger之getAndIncrement
我们再来看文章最开始举的🌰,对于i++这种线程不安全的方式,atomicInteger又是怎么保证的呢?
这就要用到getAndIncrement
这个方法
看看底层源码
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
同样调用了unsafe的getAndAddInt
方法
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
var5表示当前对象在var2偏移地址上的最新的实际值
do…while循环满足条件:
对象var1在偏移地址var2上的值如果和我们期望的var5值不同,则需要循环(自旋操作)重新判断
如果相同,那么我们更新var5的值为var5+var4,即做了一次自增操作
06
—
总结
CAS即compare and swap,先比较再交换,是一种原子性操作,底层是利用了Unsafe类,依赖于硬件的功能,实现原子操作,CAS是一种系统原语,属于操作系统用语范畴,是由若干条指令组成的,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成数据不一致问题。
关于CAS还有一些内容,例如原子引用、自旋锁以及ABA问题,这些小为就下次再和大家分享啦~
感谢大家坚持到了最后,希望大家都能早日拿到心仪的offer~
关注六只栗子,面试不迷路,我们下次再见~
作者 栗子为
编辑 一口栗子
原文始发于微信公众号(六只栗子):深入理解Java中的CAS(一)
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/88471.html