描述
为什么用CAS?
- 为了保证合理操作JMM,使用了volatile关键字,但是volatile没有办法保证操作的原子性,故引用了AtomicInteger。
- AtomicInteger之所以能保证原子性,是因为Atomic底层调用了本地操作系统的unsafe方法
- unsafe方法可以根据内存偏移量操作内存中的值、而实现操作内存偏移量的办法就是使用CAS原子性操作
- atomicInteger.getAndIncrement()方法通过一个原子性操作实现了+1的效果,弥补了volatile的不足
发展:
- JDK 5之前Java语言是靠synchronized关键字保证同步的,这是一种独占锁,也是是悲观锁。
- jdk5增加了并发包java.util.concurrent.,其下面的类使用CAS算法实现了区别于synchronouse同步锁的一种乐观锁。
CAS是什么?
- Compare and Swap——比较再交换
- 内存中修改数据后会刷新到主存中,CAS机制是在刷新前确认一件事:主存中的内容是不是和最开始拷贝给工作内存的内容一样,这叫比较。如果一样,则把工作内存更新后的值赋给主存,这叫交换。
- CAS方法返回的值是布尔型,交换成功返回true,否则返回false
工作原理
AtomicInteger源码,里面有unsafe。unsafe来自于Rt.jar。是java调用C++的native方法操作内存的标志。它可以通过操作valueOffset(内存偏移值)来定位变量在内存中的位置,直接在内存中操作变量,比如+1,这一操作是由原子性的。
-
为什么是原子的?因为CAS是CPU的操作原语,原语执行时不会被打乱顺序,即原子性的。
-
再点进getAndIncrement方法,发现它底层调用了getAndAddInt
-
进入getAndAddInt才发现,这里用了一个自旋锁。比较和交换的逻辑写在这里。
-
比较的是期望值和主存的值是否一致,如果一致更新工作线程的值,如果不一致取出主存的值。取出主存的值后作为期望值,再运算出工作线程的最新值,在自旋锁中又循环了一遍后发现期望值和主存值相等了,这回就可以更新了
优缺点
优点:
- 直接操作内存,效率非常高。
- 保证了操作的原子性
缺点:
- 如果CAS不是一下就成功,会无限循环,循环会耗费时间(自旋锁)
- 一次性只能保证一个共享变量的原子性
- 会出现【ABA】问题
ABA问题
描述:
- ABA问题出现的原因是CAS只比较工作内存一开始从主存中取出的值是否和更新前主存的值一致。只要数一样就执行“比较并交换”操作,但这样显然不合理。
举个栗子:
- 下面我们使用AtomicReference来模拟ABA问题,AtomicReference功能和AtomicInteger类似,只不过一个是作用在引用类型上,另一个是作用在int类型上。
public class ABA {
static AtomicReference<Integer> atomicReference = new AtomicReference<Integer>(100);
public static void main(String[] args) {
new Thread(()->{
atomicReference.compareAndSet(100,101