CAS你以为你真的懂?

Orianna ·
更新时间:2024-09-20
· 683 次阅读

CAS是个啥

CAS(Compare and swap)直译过来就是比较和替换,也有人叫compare and exchange,是一种通过硬件实现并发安全的常用技术,底层通过利用CPU的CAS指令对缓存加锁或总线加锁的方式来实现多处理器之间的原子操作。仔细观察J.U.C包中类的实现代码,会发现这些类中大量使用到了CAS,所以CAS是Java并发包的实现基础。它的实现过程是,有3个操作数,内存值V,旧的预期值E,要修改的新值N,当且仅当预期值E和内存值V相同时,才将内存值V修改为N,否则什么都不做。

一图看懂CAS的操作流程 从实例中找不同:CAS和重量级锁

下面看两个测试代码:

public class CasAndUnsafe_01 { private static int m = 0; public static void main(String[] args) throws InterruptedException { Thread[] threads = new Thread[100]; final CoutDownLatch latch = new CoutDownLatch(threads.length); Object o = new Object(); for(int i = 0; i { synchronized (o){ for(int j = 0; j t.start()); latch.await(); System.out.println(m); } }

代码很简单,每个线程都对m做++操作,众所周知,由于m++不是原子操作,从CPU级别来看,m++经历了3步,取,加,存3步操作,所以在这之间就有可能出现线程并发的问题。加上一个synchronized 重量级锁就避免了这个问题。

如果不想用synchronized,不想加锁,可能很多人都用过AtomicInteger。实现如下

public class AtomicInteger_01 { private static AtomicInteger m = new AtomicInteger(0); public static void main(String[] args) throws InterruptedException { Thread[] threads = new Thread[100]; CountDownLatch latch = new CountDownLatch(threads.length); Object o = new Object(); for(int i = 0; i { for(int j = 0; j t.start()); latch.await(); } }

AtomicInteger 是JUC出来后为大家提供的原子类,这里面的操作都是原子操作, m.incrementAndGet()增加并获取,这就是一个CAS操作,我们再也不用加锁了。

PS:很多人喜欢叫CAS为无锁,我很不喜欢这个叫法,准确的说CAS是自旋锁。为什么说不能叫无锁呢?

我们从源码探究一下CAS底层是怎么实现的:从代码示例2的 m.incrementAndGet()我们跟进去。

/** * Atomically increments by one the current value. * * @return the updated value */ public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; }

incrementAndGet你会发现它调用了一个类,这个类叫unsafe,调用了unsafe类里面的getAndAddInt。我们跟着进入到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; }

这时候你发现getAndAddInt方法中调用了compareAndSwapInt方法,从名字不难看出这是一个CAS操作,操作的什么类型呢?Int类型,那么具体怎么操作呢,我们进入到这个方法内。

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

当你跟到这里的时候,你发现这是native 修饰的方法,native什么意思啊,native说明已经跟到C++的代码了。我们就来看一下C++中是怎么实现的CAS。下面是unsafe.cpp中代码实现。

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x)) UnsafeWrapper("Unsafe_CompareAndSwapInt"); oop p = JNIHandles::resolve(obj); jint* addr = (jint *) index_oop_from_field_offset_long(p, offset); return (jint)(Atomic::cmpxchg(x, addr, e)) == e; UNSAFE_END

所以你在java中调用了compareAndSwapInt的话,实际上是调用了Unsafe_CompareAndSwapInt名字都一样,由于调用链很长我就不一一贴代码了,感兴趣的朋友可以自己找源码跟一下,我们直接跳到最根上。 

jdk8u:atomic_linux_x86.inline.hpp 的93行

inline jint Atomic::cmpxchg(jint exchange_value, volatile jint* dest, jint compare_value) { int mp = os::is_MP(); __asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)" : "=a" (exchange_value) : "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp) : "cc", "memory"); return exchange_value; }

我们从文件名字 atomic_linux_x86.inline.hpp 中就能获取到一些信息,说明到现在位置已经跟具体的平台和具体的cpu的型号关系了,在x86平台上linux版本的实现,那么CAS是怎么实现的呢?

看这条指令__asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"  不知道你明白没有,看到这我们可以下一条结论,原来在CPU层级有一条指令,叫cmpxchg。

到这一步,你是不是觉得自己已经对于CAS已经理解的很透彻了呢?

你有没有想过cmpxchg这条指令是原子操作吗?

这个图怎么解释呢,就是一个线程a 取到了0的值,把0改为了1 ,正要把新值写回内存的时候,线程2抢先一步修改了内存中0的值,会不会有这样的情况发生呢,答案是肯定的,在并发层级很高的情况下,这种情况是完全可能发生的。问题就在于cmpxchg这条指令是不是原子性的。虽然在汇编层级有一条指令支持CAS,但是很遗憾它不是原子性的,那么怎么保证这条指令是原子性的呢?

不知道你们有没有注意到LOCK_IF_MP(%4),从外观来看像是加锁的操作,我们进到LOCK_IF_MP这个方法内

#define LOCK_IF_MP(mp) "cmp $0, " #mp "; je 1f; lock; 1: "

前面的汇编我们忽略,你一定注意到了lock;1这是什么意思呢,从方法名字看lock  if mp ,mp的全称是Multi Processor,多cpu,意思是什么呢,就是看你操作系统有多少个处理器,若果只有一个cpu一核的话就不需要原子性了,一定是顺序执行的,如果是多核心多cpu前面就要加lock,所以最红能够实现CAS的汇编指令就被我们揪出来了。最终的汇编指令是lock cmpxchg 指令,lock指令在执行后面指令的时候锁定一个北桥信号(锁定北桥信号比锁定总线轻量一些,感兴趣的自己百度)

所以如果你是多核或者多个cpu,CPU在执行cmpxchg指令之前会执行lock锁定总线,实际是锁定北桥信号。我不释放这把锁谁也过不去,以此来保证cmpxchg的原子性。

总结 CAS并不是真的无锁。 记住这条指令lock cmpxch!lock cmpxchg!lock cmpxchg!重要的事情说三遍!面试中这就是你的亮点! 谈点私事

如果你也跟小码一样喜欢钻研,喜欢刨根问底,探究一些底层原理,乐于分享,关注我的原创公众号【小码逆袭】,小码每天都会分享技术干货文章。我还通宵为大家准备了一大波学习资源哦,都是精品,公众号回复:我爱学习   就能免费领取啦。


作者:小码Blog



cas

需要 登录 后方可回复, 如果你还没有账号请 注册新账号