CAS算法
今天在看了《Java并发编程的艺术》,学习如何减少上下问切换的时候,里面说到了通过CAS算法来更新数据,而它不需要加锁。不太理解什么是CAS算法,所以在网上搜罗半天资料,看了很久才理解了,给大家整理了一下CAS算法。
1、概述
CAS的全程是:Compare And Swap(比较并交换),CAS是实现并发计算时常用到的技术,Java并发包中的很多类都使用了CAS技术,如ConcurrentHashMap,AtomicInteger原子操作等
CAS操作涉及到3个操作符:当前内存中的值、预估值、即将修改的新增,当且仅当预估值等于内存中的值的时候,才将新的值保存到内存中,否则什么都不做
2、作用
CAS可以将比较和交换转换为原子操作,这个原子操作直接由CPU保证,CAS可以保证共享变量赋值时的原子操作
3、特点
CAS是一种非阻塞算法的实现,它能在不使用锁的情况下实现多线程安全,所以CAS也是一种无锁算法
一个线程失败或挂起并不会导致其他线程也失败或挂起
4、案例
AtomicInteger的incrementAndGet()源码
-
AtomicInteger底层使用到了Unsafe类,它提供了原子操作,Unsafe类可以直接操作内存空间的值
-
Unsafe的CompareAndSwapInt()方法其实就是CAS方法,它会自旋判断当前内存中的值与预期的值是否相等,如果相等就会把内存中的值用更新值替换,如果不相等则不操作,以此来保证原子性
-
基于硬件平台的汇编指令,在intel的CPU中,使用的是cmpxchg指令,也就是说CAS靠硬件实现,从而在硬件层面提升效率
5、优点
- 由于CAS是非阻塞的,可以避免优先级倒置和死锁等问题
- 性能好,使用无锁的方式,没有锁竞争带来的系统开销,也没有线程间频繁调度带来的开销
6、缺点
- 循环时间长开销大(自旋)
- 如果CAS失败,会一直进行尝试,如果CAS长时间一直不成功,可能给CPU带来很大的开销
- 解决方法:破坏调死循环,当超过一定时间或者一定次数时,return退出
- 只能保证一个共享变量的原子操作
- 当对一个共享变量操作时,可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性
- 解决方法:
- 用锁来保证原子性
- 把多个共享变量合成一个共享变量来操作
- 封装成对象 ;Java1.5开始,JDK提供了AtomicReference类来保证引用对象之前的原子性,可以把多个变量放到一个对象里来进行CAS操作
- ABA问题
-
如果内存地址V初次读取的值是A,并且在准备赋值的时候检查到它的值仍然为A,那我们就能说明它的值没有被其他线程改变过吗?
如果在这段期间它的值曾经被改称了B,然后又改为A,那CAS就会误认为它从来没有被修改过,这就是ABA问题
-
解决方法
- Java并发包提供了一个带有标记的原子引用类AtomicStampedReference,它可以通过控制变量值的版本来保证CAS的正确性;即在变量前面添加版本号,每次变量更新的时候都把版本号+1,这样变化过程就变成A-B-A变成1A-2B-3A
- 增加时间戳