什么是CAS?
Compare and Swap (比较交换),使用CAS来保证线程的对变量的原子操作,避免被其他线程所干扰。
CAS原理:其中包含三个参数
要修改的变量
变量的预期值
要更性的参数
在更新数据时,CAS先判断变量中的值与预期值,如果一致则更新为新值,如果变量的值与预期值不 一致,表示当前操作被其他线程干扰,此时线程不会阻塞而是自旋等待(自旋锁),重复以上操作,CAS操作即 使没有锁,也可以发现其他线程对当前线程的干扰,并进行恰当的处理。
CAS在Java层面虽然没有使用锁保证原子性,但是在底层汇编对“比较替换”进行加锁保证其原子性操作。
CAS可能存在的问题
CPU开销较大
在并发较高的情况下,如果许多线程反复尝试更改一个变量,却一直更新不成功,循环往复,会给CPU带来很大压力。看可以通过设置默认自旋次数来减少CPU消耗。
不能保证代码原子性
CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用synchronized了。
ABA问题
这是CAS机制存在的最大的问题。
ABA问题
什么是ABA问题?
-在使用CAS进行变量的原子性操作时,先要获取一个预期值,然后再判断预期值与实际的值是否相同,如果相同则继续修改变量。由于CAS操作没有使用重量级锁,如果获得一个预期值后,实际的值被其他线程篡改为其他值,然后又被改回到原有的值,对于原线程来说,实际值和预期值是一样的,但实际上值已经被修改过。这样的问题就是ABA问题
例如:t1线程进行CAS操作,获取的预期值为"A",在t1线程进行"比较交换"前,t2线程进入并将变量的值 由"A"改为"B",然后又重新改回到"A"并退出,此时对于t1线程来说,实际值和预期值都是"A",就可以 做"比较并交换"操作,但实际上,实际值"A"已被修改过。
如何解决ABA问题
上述例子看似没有问题,但是结合实际就会发现其问题所在,举例如下:
,假设有一个遵循CAS的取款机,小明去取款机取款,此使账户余额100元,小明要取款50,但是由于故障取款机执行了两次取款线程:
线程1:取款50,余额50
线程2:取款50,余额50(失败)
理想状况下之只能有一个线程执行成功,另一个线程执行失败,进入等待,但是此时小明的母亲给小明转账50元,此时:
线程3:存款50,余额100元
由于线程3执行成功,小明账户余额又到达了100元,此时线程2,通过cas比较交换,发现前后数据一致,所以可以进行数据改写.如下:
线程2:取款50,余额50
此时不难发现小明的余额少了50元.这就是ABA问题.那么我们如何解决呢?
我们可以使用CAS中的"原子引用"来解决ABA问题.
原子引用是基于乐观锁的思想,为CAS中的每个操作添加一个版本号,每次执行成功之后,都会对版本号进行+1操作,每次更新数据之前都会对比版本号,观察是否有其他线程进行干扰.如果版本号一制则继续,不一致进入自旋.
Java中提供了AtomicStampedReference类来实现原子引用并可以设置版本号