目录
- 一.什么是CAS?
- 二.CAS实现过程
- 三.CAS的缺点
- 1.循环时间长
- 2.只能保证一个共享变量是原子操作
- 3.ABA问题和解决方法
- 四.拓展题
- 1.i++和++i是原子性操作吗?
- 2. i++ 不加lock和synchronized怎么保证原子性?
一.什么是CAS?
CAS(Compare And Swap): 比较并替换,是一种无锁算法。在不使用锁的情况下实现多线程之间的变量同步。
V: 主存的值
E: 预期的值
N: 新值
二.CAS实现过程
注意:
① 在线程开启的时候,会从主存中给每个线程拷贝一个变量副本到本线程各自的运行环境中。
② 失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次发起尝试。
并发环境: 执行 +1 操作,主存 V 初始值为 0,有两个线程 T1和T2。各自副本变量V1、V2为0,假设T1先拿到执行权
第一步: T1 读取当前 V1 的值赋值给 E1。
第二步: T1 执行 +1 操作,N1 = E1 + 1。
第三步: E1 和 主存 V比较,发现 E1 = V。
第四步: 将N1,写入主存,主存 V 变为 1。
第五步: T2拿到执行权,T2读取当前V2 的值赋值给 E2(这时V2还是0)。
第六步: T2 执行 +1 操作,N2 = E2 + 1。
第七步: E2 和 主存 V 比较,发现 E2 != V。
第八步: 重新获取 主存的值,V = 1,赋值给 V2,V2 赋值给E2。
第九步: T2 执行 +1 操作,N2 = E2 + 1。
第十步: E2 和 主存 V比较,发现 E2 = V。
第十一步: 将N2,写入主存,主存 V 变为 2。完成任务。
三.CAS的缺点
1.循环时间长
如果很多线程都去修改一个值,就会一直不成功一直尝试。
2.只能保证一个共享变量是原子操作
3.ABA问题和解决方法
原因: 线程1操作变量改为A,线程挂起,然后线程2操作变量改为X,又改为Z,最后改为A,其实中间变量的值已经被改变。
解决:
① 使用 AtomicStampReference 类,增加了一个标记 stamp,可以判断数据有没有被修改过。
public class AtomicStampedReference<V> {
private static class Pair<T> {
final T reference;
final int stamp;
private Pair(T reference, int stamp) {
this.reference = reference;
this.stamp = stamp;
}
static <T> Pair<T> of(T reference, int stamp) {
return new Pair<T>(reference, stamp);
}
}
② 可以使用版本号,其实和上面stamp原理差不多。
四.拓展题
1.i++和++i是原子性操作吗?
不是原子性操作。不是一条单独的指令。是三条指令
第一步:获取这个值
第二步:+1
第三步:写回这个值
2. i++ 不加lock和synchronized怎么保证原子性?
有Atomic类里面有AtomicInteger类(底层是CAS)。
//提供了硬件级别的原子操作,方法都是 native 修饰
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
public final boolean compareAndSet(int expect, int update) {
//valueOffset=V,E=expect,N=update
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}