并发系列之CAS与原子操作
- 1、CAS的概念
- 2、Java实现CAS的原理 - Unsafe类
- 3、原子操作-AtomicInteger类源码简析
- 4、CAS实现原子操作的三大问题
- 4.1、ABA问题
- 4.2、循环时间长开销大
- 4.3、只能保证一个共享变量的原子操作
- 5、往期佳文
- 5.1、面试系列
- 5.2、技术系列
- 5.3、源码系列
- 5.4、数据结构和算法系列
- 5.5、多线程系列
1、CAS的概念
CAS的全称是:比较并交换(Compare And Swap)。在CAS中,有这样三个值:
- V:要更新的变量(var)
- E:预期值(expected)
- N:新值(new)
比较并交换的过程如下:
判断V是否等于E,如果等于,将V的值设置为N;如果不等,说明已经有其它线程更新了V,则当前线程放弃更新,什么都不做。所以这里的预期值E本质上指的是“旧值”。
例子:
- 如果有一个多个线程共享的变量i原本等于5,我现在在线程A中,想把它设置为新的值6;
- 我们使用CAS来做这个事情;
- 首先我们用i去与5对比,发现它等于5,说明没有被其它线程改过,那我就把它设置为新的值6,此次CAS成功,i的值被设置成了6;
- 如果不等于5,说明i被其它线程改过了(比如现在i的值为2),那么我就什么也不做,此次CAS失败,i的值仍然为2。
(在这个例子中,i就是V,5就是E,6就是N。)CAS是一种原子操作,它是一种系统原语,是一条CPU的原子指令,从CPU层面保证它的原子性。
当多个线程同时使用CAS操作一个变量时,只有一个会胜出,并成功更新,其余均会失败,但失败的线程并不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。
2、Java实现CAS的原理 - Unsafe类
在Java中,如果一个方法是native的,那Java就不负责具体实现它,而是交给底层的JVM使用c或者c++去实现。
在Java中,有一个Unsafe类,它在sun.misc包中。它里面是一些native方法,其中就有几个关于CAS的(Unsafe中对CAS的实现是C++写的,它的具体实现和操作系统、CPU都有关系。):
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
Linux的X86下主要是通过cmpxchgl这个指令在CPU级完成CAS操作的,但在多处理器情况下必须使用lock指令加锁来完成。
当然,Unsafe类里面还有其它方法用于不同的用途。比如支持线程挂起和恢复的park和unpark, LockSupport类底层就是调用了这两个方法。还有支持反射操作的allocateInstance()方法。
3、原子操作-AtomicInteger类源码简析
JDK提供了一些用于原子操作的类,在java.util.concurrent.atomic包下面。在JDK 8中,有如下17个类:
从名字就可以看得出来这些类大概的用途:
- 原子更新基本类型
- 原子更新数组
- 原子更新引用
- 原子更新字段(属性)
以AtomicInteger类的getAndAdd(int delta)方法为例(U其实就是一个Unsafe对象):
private static final Unsafe unsafe = Unsafe.getUnsafe();
public final int getAndAdd(int delta) {return unsafe.getAndAddInt(this, valueOffset, delta);
}
public final int getAndAddInt(Object var1, long var2, int var4) {int var5;do {var5 = this.getIntVolatile(var1, var2);} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));return var5;
}
4、CAS实现原子操作的三大问题
4.1、ABA问题
所谓ABA问题,就是一个值原来是A,变成了B,又变回了A。这个时候使用CAS是检查不出变化的,但实际上却被更新了两次。
ABA问题的解决思路是在变量前面追加上版本号或者时间戳。从JDK 1.5开始,JDK的atomic包里提供了一个类AtomicStampedReference类来解决ABA问题。
这个类的compareAndSet方法的作用是首先检查当前引用是否等于预期引用,并且检查当前标志是否等于预期标志,如果二者都相等,才使用CAS设置为新的值和标志。
public boolean compareAndSet(V expectedReference,V newReference,int expectedStamp,int newStamp) {Pair<V> current &#61; pair;returnexpectedReference &#61;&#61; current.reference &&expectedStamp &#61;&#61; current.stamp &&((newReference &#61;&#61; current.reference &&newStamp &#61;&#61; current.stamp) ||casPair(current, Pair.of(newReference, newStamp)));
}
4.2、循环时间长开销大
CAS多与自旋结合。如果自旋CAS长时间不成功&#xff0c;会占用大量的CPU资源。
解决思路是让JVM支持处理器提供的pause指令。pause指令能让自旋失败时cpu睡眠一小段时间再继续自旋&#xff0c;从而使得读操作的频率低很多,为解决内存顺序冲突而导致的CPU流水线重排的代价也会小很多。
4.3、只能保证一个共享变量的原子操作
两种解决方案&#xff1a;
- 使用JDK 1.5开始就提供的AtomicReference类保证对象之间的原子性&#xff0c;把多个变量放到一个对象里面进行CAS操作&#xff1b;
- 使用锁。锁内的临界区代码可以保证只有当前线程能操作。
5、往期佳文
5.1、面试系列
1、吊打面试官之一面自我介绍
2、吊打面试官之一面项目介绍
3、吊打面试官之一面系统架构设计
4、吊打面试官之一面你负责哪一块
5、吊打面试官之一面试官提问
6、吊打面试官之一面你有什么问题吗
······持续更新中······
5.2、技术系列
1、吊打面试官之分布式会话
2、吊打面试官之分布式锁
3、吊打面试官之乐观锁
4、吊打面试官之幂等性问题
5、吊打面试关之分布式事务
6、吊打面试官之项目线上问题排查
······持续更新中······
5.3、源码系列
1、源码分析之SpringBoot启动流程原理
2、源码分析之SpringBoot自动装配原理
3、源码分析之ArrayList容器
4、源码分析之LinkedList容器
5、源码分析之HashMap容器
6、源码分析之ConcurrentHashMap容器
7、源码分析之五种Map容器的区别
······持续更新中······
5.4、数据结构和算法系列
1、数据结构之八大数据结构
2、数据结构之动态查找树&#xff08;二叉查找树&#xff0c;平衡二叉树&#xff0c;红黑树&#xff09;
······持续更新中······
5.5、多线程系列
1、并发系列之初识多线程
2、并发系列之JMM内存模型
3、并发系列之synchronized解析
4、并发系列之volatile解析
5、并发系列之synchronized与volatile的区别
6、并发系列之Lock解析
7、并发系列之synchronized与lock的区别
······持续更新中······