CAS的底层剖析

CAS

1、什么是CAS?

CAS:又叫 campare and set/Swap/Exchange,自旋锁。

再增加一个知识点:

悲观锁:认为该操作被别的线程打断;

乐观锁:认为该操作不会被别的线程打断,会消耗CPU资源;

注:悲观锁,各线程在队列里等待,不消耗CPU资源;而乐观锁时,各线程不断轮询,会消耗CPU资源。使用时理论上可通过压测来决定,但是一般情况下,都是直接使用Synchronized来解决数据同步问题,因为Synchronized经过这么长时间的改进,已经得到了很大的优化。

这里我举个例子来说明悲观锁和乐观锁:

顾名思义,悲观锁就很悲观,它总是认为别人会来抢锁;乐观锁就很乐观,总是认为别人不会来抢锁,很文明。

以上厕所为例:

悲观锁上厕所蹲坑,进去后由于害怕别人直接推门而进粗暴地和它抢马桶,于是便进门便插上门栓,以防止别人进来,然后厕所门上红灯亮起表示有人使用。而其他人想要上厕所时看到门上红灯亮起,故在外面排队等着,等里面的人出来以后,下一个再进去。

乐观锁上厕所蹲坑,进去后由于知道大家都很文明,不会粗暴地直接闯入,而是在推门之前会先敲门询问里面是否有人正在使用,所以进入厕所以后并不会锁门,而是直接开始噗噗噗,多好啊,省时间,哈哈哈。但是每一个来上厕所的人都是很急的,虽然大家都很文明,但是人有三急。所以,后来上厕所的人在外面会不断地敲门询问里面的使用者是否使用完毕。

ABA 问题的解决办法:加版本号。如,每改变一次,自身版本号就 加1。而是否对最后结果有影响(是否更新为新值)需要程序员自己判断。

如果程序员觉得没什么影响,只要结果还是A那就行,如果程序员觉得如果有改变,心里膈应,那就不行。

 

2、使用原子类的底层实现原理

以AtomicInteger 为例:

代码如下,为了实现线程安全,我们使用了原子类AtomicInteger ,而且变量增加使用了AtomicInteger 类的类方法 incrementAndGet()。

 接下来我们就对incrementAndGet() 一探究竟。

按住ctrl + 单击该方法,跳转到AtomicInteger.java类中:

 再点进去unsafe.getAndAddInt()方法,跳转到unsafe.class类中:

 这里有一个循环,条件是做一个CAS 操作。再点击进去,找到是当前类的一个native方法。

 这是什么意思呢?native表示这是用C/C++写的底层代码调用。

似乎到这里断了,不过为了一探究竟,我们继续找到了C代码。

在Java里是unsafe类的compareAndSwapInt(),在底层代码里对应的是unsafe.cpp文件中:

  我们观察到第1217行可知:最后调用的是Atomic::cmpxchg()方法,熟悉C/C++的话,就知道,这是stomic.cpp文件中的cmpxchg方法。

再点进去追击:

 可看到第56行是cmpxchg()方法,一路追踪,我们可发现,到最后是到了atomic_linux_x86.inline.cpp文件中。

找到Atomic.cpp文件中的cmpxchg()方法。

 在这里我们看:

在第95行有个LOCK_IF_MP() ,这其实是个宏定义,意思是是否是多核(Multi Processor):

再继续追击,最后,我们知道最终的实现是依靠底层的 lock cmpxchg  指令(CPU级别)。 

虽然我们从代码可知,在多核(CPU)时才加lock,单核(CPU)不加。如:6 核12线程,我们在这里视为12核。故,单核情况下,指的就是自己线程不会打断自己。

cmpxchg 并不是原子性的,保证原子性还是依靠前面的 Lock。所以,我们才说底层代码直接支持CAS,是一把悲观锁,可以是缓存锁,也可以是总线锁。

 

 

 

 

Over......

posted @ 2021-08-15 22:22  额是无名小卒儿  阅读(177)  评论(0编辑  收藏  举报