一、什么是CAS
CAS的执行原理:CAS 操作包含三个操作数,内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置的值即可。”
二、JDK中的CAS
java.util.concurrent.atomic 包下的类都是基于CAS实现的
由源码我们可以看出JDK的CAS使用的unsafe中的方法,他的实现是本地方法,也就是说JDK的CAS采用的是CPU提供的CAS原语操作,并不是JDK自己实现的。
三、CAS的缺点
1、cpu开销大:在高并发下,许多线程,更新一变量,多次更新不成功,循环反复,给cpu带来大量压力。
限制循环次数可以避免开销,JAVA中的自旋锁。java中的自旋锁是一种假设在不久将来,当前的线程可以获得锁,因此虚拟机会让当前想要获取锁的线程做几个空循环(这也是称为自旋的原因),在经过若干次循环后,如果得到锁,就顺利进入临界区。如果还不能获得锁,那就会将线程在操作系统层面挂起,这种方式确实也是可以提升效率的。但问题是当线程越来越多竞争很激烈时,占用CPU的时间变长会导致性能急剧下降,因此Java虚拟机内部一般对于自旋锁有一定的次数限制,可能是50或者100次循环后就放弃,直接挂起线程,让出CPU资源。
2、ABA问题:比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。虽然程序正常执行了但是内存位置V中的A已经不是原来的那个A了。由于ABA问题带来的隐患,各种乐观锁的实现中通常都会用版本戳version来对记录或对象标记,避免并发操作带来的问题,在Java中,AtomicStampedReference<E>也实现了这个作用,它通过包装[E,Integer]的元组来对对象标记版本戳stamp,从而避免ABA问题
AtomicStampedReference的构造函数中需要一个初始值和一个版本号
compareAndSet()方法:置换前必须比较期望值与当前值以及期望值的版本号与当前值得版本号
- V expectedReference:期望值
- V newReference:新值
- int expectedStamp:期望版本号
- int newStamp:新值得版本号