一、引言
前面的并发编程学习多次提到的CAS这个原理,也参考了不少CAS的文章,想想还是自己写一篇总结好好理解一下CAS原理吧,作为并发包的基石,CAS原理在提升性能方面是有很大的用处的,很有必要去专门下功夫了解一下。
二、CAS的相关概念
基础概念
CAS:Compare and Swap,即比较再交换。
主要步骤:冲突检测和数据更新。
用处:可以实现乐观锁。
使用CAS之前存在的问题
java在1.5之前都是靠synchronized关键字保证同步,synchronized是悲观锁,保证了无论哪个线程持有共享变量的锁,都会采用独占的方式来访问这些变量。这种情况下:
- 1、在多线程竞争下,加锁、释放锁会导致较多的上下文切换和调度延时,引起性能问题。
- 2、如果一个线程持有锁,其他的线程就都会挂起,等待持有锁的线程释放锁。
- 3、如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能风险。
简单的来说就是会存在很多性能问题。
CAS原理分析
核心操作:冲突检测和数据更新。
特点:当多个线程尝试使用CAS同时更新同一个变量时,只有一个线程可以更新变量的值,其他的线程都会失败,失败的线程并不会挂起,而是告知这次竞争中失败了,并可以再次尝试。
三个操作数:
- 需要读写的内存位置(V)
- 预期原值(A)
- 新值(B)
一句话理解:我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。
PS:乐观锁是一种思想,CAS只是这种思想的一种实现方式。、
伪代码:
//伪代码,解析CAS过程 do{ //1、备份旧数据(V) //2、把旧值设置为新值(B) //3、返回结果【成功返回新值(B),失败则返回旧的值(V)】 }while(/*比较旧值(V)和预期值(A)是否相等*/);
图解:
场景:t1,t2线程同时更新同一变量100的值
解析:
- 1、因为t1和t2线程都同时去访问同一变量100,所以他们会把主内存的值完全拷贝一份到自己的工作内存空间,所以t1和t2线程的预期值都为100。
- 2、假设t1在与t2线程竞争中线程t1能去更新变量的值,而其他线程都失败。PS:失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次发起尝试
- 3、t1线程去更新变量值改为101,然后写到内存中。此时对于t2来说,内存值变为了101,与预期值100不一致,就操作失败了【想改的值不再是原来的值】
- 4、CAS 操作是基于共享数据不会被修改的假设,采用了类似于数据库的 commit-retry 的模式,当同步冲突出现的机会很少时,这种假设能带来较大的性能提升。
三、CAS的应用
PS:Unsafe为我们提供了硬件级别的原子操作。
四、ABA问题
问题的产生
首先我们看一下ABA问题是怎么产生的,网上有一个很好的例子,这里用来借鉴一下,感兴趣的同学可以点击参考文章中的【漫画:什么是CAS机制】来详细了解,作者讲解得非常好,强烈推荐关注下原文作者“程序员小灰”:
解决方法
解决方法:借助AtomicStampedReference类,加入版本号进行比较。
原理:
- 需要读写的内存位置(V)
- 预期原值(A)
- 新值(B)
- 版本号(version)
在原来的基础上加入版本号,读取公共变量的时候把当前版本号读取出来,比较的时候不仅比较内存位置值和预期原值,还要比较版本号,三者都相同时才进行替换。
PS:详解AtomicStampedReference戳这里~
参考文章: