1. CAS
CAS(Compare-And-Swap)比较并交换,是一条CPU的原子指令,其作用是让CPU先进行比较两个值是否相等,然后原子地更新某个位置的值,CAS操作需要输入两个数值,一个旧值(期望操作前的值)和一个新值,在操作期间先比较下在旧值有没有发生变化,如果没有发生变化,才交换成新值,发生了变化则不交换。
在多线程环境下,我们要实现变量进行自增而且还要保证其他线程不会读到脏数据,那么会想到使用锁来实现,但是加锁就存在性能问题。所以我们就可以选择使用CAS来实现原子的更新数据。CAS操作的效率是非常高的。
1.1 使用CAS存在的问题
-
ABA 问题
因为根据CAS操作的定义可以知道,CAS操作会检查值有没有发生变化,没有则更新,但是一个值原来是A 更新成为了B后,又被更新成了A,那么这个时候使用CAS检查的时候就会认为没有发生变化,但实际上是更新了值的。
解决的思路就是使用版本号。在变量前追加版本号,每次变量更新的时候把版本号加1,那么A->B->A就会变成1A->2B->3A。在JDK1.5之后Atomic包里提供了一个AtomicStampedReference累解决ABA问题,这个类的compareAndSet方法的作用是首先检查当前引用是否等于预期引用,并且检查当前标志是否等于预期标志,如果全部相等,则以原子的方式将该引用和该标志的值设置为给定的新值。 -
循环时间长开销大
自旋CAS如果长时间不成功,会给cpu带来非常大的执行开销。 -
只能保证一个共享变量的原子操作
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对于多个共享变量来说,CAS就无法保证操作的原子性,这个时候就要使用锁了,但是有一个比较巧妙地方法,就是把多个变量合并成一个变量来操作,从JDK1.5开始提供AtomicReference类保证引用对象之间的原子性,就可以把多个变量放在一个对象来进行CAS操作。
2. 原子类AtomicInteger
public final int get():获取当前的值
public final int getAndSet(int newValue):获取当前的值,并设置新的值
public final int getAndIncrement():获取当前的值,并自增
public final int getAndDecrement():获取当前的值,并自减
public final int getAndAdd(int delta):获取当前的值,并加上预期的值
void lazySet(int newValue): 最终会设置成newValue,使用lazySet设置值后,可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
在查看源码会发现底层就是将变量用volatile修饰,一个线程修改了数据可以保证其他线程可以马上看到修改后的值,在使用CAS操作原子性的更新值。
网友评论