白话CAS(比较与交换,Compare and swap)算法

在之前了解了volatile关键字后,就要讲到Java中是如何使用这个关键字的呢,就和网上大多数文章一样,volatile关键字最主要用在原子变量和原子引用上的使用。可能将说到原子变量(AtomicInteger)可能大多数的人在项目中还真就使用过,但是可能不理解其中的原理,其实很简单,原子变量和原子引用就包含两部分:一部分就是,volatile的可见性,在前两篇文章已经彻底的分析了一遍,另一部分就是通过某种方式保证变量修改的三个步骤的原子性,如何保证呢,就要引出CAS算法

CAS算法是什么

CAS(比较与交换,Compare and swap) 是一种有名的无锁算法(后面会提到什么是无锁(lock-free))。CAS是一种CPU级别的指令,在大多数处理器架构,包括IA32、Space中采用的都是CAS指令。顾名思义这个算法分为两部分,比较和交换,一共有三个数据,A数据,B数据,C数据

A数据是内存的最新值,是volatile类型的,对于所有线程都是可见的
B是相当于是A的快照数据(一份缓存)
C是A期望的值,也就是结果值

开始有两个线程,同时需要修改内存中的同一个变量S,两个变量要同时对S的值加一
正常情况下,第一个线程拿到内存中的最新值放到A(对于所有线程是可见的)中,同时将这个最新值打一个快照将值放到B中,然后对B中的值进行加一操作结果放到C中,判断A中的值和B中的值是否相等,这时A的值和B的值是相等的,将C的值放到A中,其他线程实时看到A的最新值,第二个线程在第一个线程之后执行相同的操作没什么问题。
上图理解一下:
白话CAS(比较与交换,Compare and swap)算法_第1张图片

但是在并发情况下,第一个线程拿到内存中的最新值放到A中,第二个线程同样拿到内存中的罪行值放到A中,两个线程同时将这个最新值打一个快照将值放到B中,然后对B中的值进行加一操作结果放到C中,线程一判断A中的值和B中的值是否相等,这时A的值和B的值是相等的,将C的值放到A中,让其他线程实时看到A的最新值,但是这个时候线程二发现A的值已经和B的值不相等了,那么线程二中之前的操作全部作废,重新再来一遍,线程二再次拿到最新的值放到A中,同时将快照放到B中,加完一之后的值放到C中,判断A和B的值,此时没有人修改内存中的值,A和B的值相等,将C的值放到A中,修改内存中的变量。

上图理解一下:
白话CAS(比较与交换,Compare and swap)算法_第2张图片

通过上面的比较和交换,附加回滚操作,使得多线程之间的并发问题得以解决。
当通过以上方式理解透彻了之后,就可以明白实际上CAS操作是通过对内存中的值缓存(B),然后将操作好的数据缓存(C),判断内存中最新的值是否与自身缓存的值(B)相等,如果相等说明自己缓存的是最新的值,如果不相等,说明自己缓存的值已经是过期的数据,需要重新执行一遍刚才的操作,这就是CAS算法(是不是很简单,虽然这样说很简单,但是在实际使用过程中用起来还是比较麻烦的)。

实际上java.util.concurrent.atomic中的AtomicXXX,都使用了这些类似的算法保证了原子操作保证了多线程下并发的问题。

CAS算法的性质

通过对CAS算法的理解再理解CAS的性质就非常的容易了
1.属于乐观锁,说到乐观锁就得提一下锁的概念了,锁就是两个人同时上厕所,但是厕所只有一个,第一个人上厕所把门锁住了,第二个人就不能上,这个门锁就是我们程序中所谓的锁。

在这个基础上又产生出了乐观锁和悲观锁,还拿上面的例子举例,悲观锁就是,第一个人上厕所的时候,第二个人只能在外面等待,只能憋着什么都不做;而乐观锁就是,第一个人上厕所的时候,第二个人不断的敲门问里面的人是否上完了,这就是乐观锁。总结一下就是悲观锁悲观的认为共享的东西是有并发的,需要将共享的东西锁住,而乐观锁就是乐观的认为共享的东西是没有并发的,只有我一个人进行修改,只不过是失败了就进行不断的尝试,直至达到预期,说白了就是不断的进行想要达到效果的操作。

2.非阻塞的,因为在不断的尝试当中是没有线程被阻塞的,所有的线程都在轮循自己的任务,没有线程在进行等待,所以是非阻塞的。

3.无锁(lock-free),就是没有锁进行控制,所有的线程都执行自己的任务,都去尝试执行自己的操作。
说到无锁就不得不提wait-free,可以看到lock-free下还是有线程有可能在一直循环,导致死循环而不能退出,但是wait-free是保证可以在有限的循环内必定会结束,也就是在lock-free死循环尝试的情况下可以在有限的步骤内执行完毕(可能是10,100,1000…)。这是我的理解,而且现在关于wait-free的算法或者实现不是很多,或者wait-free的效率还不如lock-free的效率,因为wait-free虽然说是有限的步骤,但是这个有限的步骤可能非常大,导致执行起来反倒会慢,所以我也没做过多的研究。

ABA问题

其实ABA问题很简单,就是有两个线程同时修改共享变量,线程一获取到内存最新值为1,快照值也为1,想要修改为2,这时线程二也获取到最新值为1,快照为1,想要修改为2,线程二执行完成之后,将内存改为2,之后线程二又要执行一个操作,想要将值减1,这时线程二有获取最新值为2,快照值为2,想要修改为1,执行完毕后将内存最新的值修改为1,这时线程一发现内存最新值和快照值相等,将内存值修改为2。可以发现线程二执行减1的操作对于线程一来说丢失了,此时就会产生问题,达不到我们预期想要达到的效果。

白话CAS(比较与交换,Compare and swap)算法_第3张图片

可能有的人可能要问了,线程一想要进行加1的操作,线程二想要加1后减1,最后的结果不应该是2么,应该没什么问题,但是如果线程一和线程二修改的是引用中的内容呢,线程二已经将引用修改了,但是线程一感知不到,还要去修改原来引用中的内容,这时就可能导致访问野指针,进而访问不到数据。实际上产生这个问题的原因就是在进行CAS的过程中无法判断这个变量是否是被别人进行了加一减一的操作,从而导致判断失误。
所以在解决此类问题的时候会在获取和赋值的时候给变量加一个版本号的东西,这时候线程一再进行判断的时候发现虽然值相同但是版本号不同也会去重新获取最新数据进行CAS操作。

你可能感兴趣的:(白话CAS(比较与交换,Compare and swap)算法)