什么是cas
在计算机科学中,比较和交换(Conmpare And Swap)是用于实现多线程同步的原子指令。
它将内存位置的内容与给定值进行比较,只有在相同的情况下,将该内存位置的内容修改为新的给定值。 这是作为单个原子操作完成的。
原子性保证新值基于最新信息计算; 如果该值在同一时间被另一个线程更新,则写入将失败。 操作结果必须说明是否进行替换;
这可以通过一个简单的布尔响应(这个变体通常称为比较和设置),或通过返回从内存位置读取的值来完成(摘自维基本科)
CAS介绍
CAS(Compare and Swap), 翻译成比较并交换。
CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
并发比较低的时候用CAS比较合适,并发比较高用synchronized比较合适
CAS的缺点
1.只能保证对一个变量的原子性操作
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。
2.长时间自旋会给CPU带来压力
我们可以看到getAndAddInt方法执行时,如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能会给CPU带来很大的开销。
ABA问题
设想如下场景:
线程1准备用CAS将变量的值由A替换为B。
在此之前,线程2将变量的值由A替换为C,又由C替换为A,
然后线程1执行CAS时发现变量的值仍然为A,所以CAS成功。
但实际上这时的现场已经和最初不同了,尽管CAS成功,但可能存在潜藏的问题。
比如:
有一个用单向链表实现的栈,栈顶为A。
线程T1获取A.next为B,然后希望用CAS将栈顶替换为B,head.compareAndSet(A,B)。
在T1执行上面这条指令之前,线程T2介入,将A、B出栈,再pushD、C、A。此时B.next为null。
此时轮到线程T1执行CAS操作,检测发现栈顶仍为A,所以CAS成功,栈顶变为B。
但实际上B.next为null,其中堆栈中只有B一个元素,C和D组成的链表不再存在于堆栈中,这样就造成C、D被抛弃的现象。
解决方法
各种乐观锁的实现中通常都会用版本戳version来对记录或对象标记,避免并发操作带来的问题,在Java中,AtomicStampedReference也实现了这个作用,它通过包装[E,Integer]的元组来对对象标记版本戳stamp,从而避免ABA问题。