1、ABA问题
使用CAS操作内存数据时,数据发生过变化也能更新成功,如操作序列A==>B==>A时,最后一个CAS的预期数据A实际已经发生过更改,但也能更新成功,这就产生了ABA问题。
ABA问题的解决思路是使用版本号。在变量前面追加上版本号,每次变量更新的时候将版本号加1,那么操作序列A==>B==>A就会变成A1==>B2==>A3,如果将A1当作A3的预期数据,就会操作失败。
JDK提供了两个类AtomicStampedReference和AtomicMarkableReference来解决ABA问题。比较常用的是AtomicStampedReference类,该类的compareAndSet()方法的作用是首先检查当前引用是否等于预期引用,以及当前印戳是否等于预期印戳,如果全部相等,就以原子方式将引用和印戳的值一同设置为新的值。
2、只能保证一个共享变量之间得原子性操作
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,CAS就无法保证操作的原子性。
一个比较简单的规避方法为:把多个共享变量合并成一个共享变量来操作。
JDK提供了AtomicReference类来保证引用对象之间的原子性,可以把多个变量放在一个AtomicReference实例后再进行CAS操作。比如有两个共享变量i=1、j = 2,可以将二者合并成一个对象,然后用CAS来操作该合并对象的AtomicReference引用。
3、开销问题
自旋CAS如果长时间不成功(不成功就一直循环执行,直到成功),就会给CPU带来非常大的执行开销。
解决CAS恶性空自旋的有效方式之一是以空间换时间,较为常见的方案为:
1)分散操作热点,使用LongAdder替代基础原子类AtomicLong,LongAdder将单个CAS热点(value值)分散到一个cells数组中。
2)使用队列削峰,将发生CAS争用的线程加入一个队列中排队,降低CAS争用的激烈程度。JUC中非常重要的基础类AQS(抽象队列同步器)就是这么做的。