站在巨人肩上操作CAS(一):CAS的原理

2020-12-06  本文已影响0人  bug音音

CAS的定义

乐观锁和悲观锁

悲观锁

常见的悲观锁

悲观锁的实现

总结:

缺点

乐观锁

思路

乐观锁应用

CAS

CAS(Compare and Swap)算法

非阻塞算法

核心参数

image

如果没有CAS这样的机制,那在多线程的场景下,两个线程同时对共享数据进行修改,很容易就出错,导致某一个线程的修改被忽略。

核心代码

//原子操作
if (A == V) {
    V = B;
    return B;
} else {
    return V;
}

变量 V

如何保证线程每次操作V时,都去主内存中获取 V 值?

如何保证在进行 V 和 A 值比较相等之后,而在 B值赋给 V 之前,V值不会被其他线程所修改?

也就是说如何保证比较和替换这两个步骤的原子性?

看一下CAS原理:

CAS原理

具体看一下:AtomicInteger类的 compareAndSet() 方法:

public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

调用的时 sun.misc 包下 Unsafe 类的 compareAndSwapInt()方法:(本地方法)

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

本地方法会调用 JDK中的几个cpp文件(这是在widowsX86中的,不同操作系统调用的文件不同,文件中的实现方式也不同);

具体的C++代码我就不贴了,知道上层的实现思路就行;

底层大致思路:通过对 cmpxchg 指令添加 lock 前缀(windowsX86中的实现),确保对主内存中 V 值的 读-改-写操作原子执行;

不同的操作系统有不同的实现方式,但大致的思路是类似的,目的也都是保证比较赋值操作是原子操作。

更详细的底层实现,以及相关的一些关于CPU锁的内容,可以去看一下《Java 并发编程艺术》。

最终的实现作用:

CAS缺点

ABA问题

问题描述

CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。

解决方案

版本号

AtomicStampedReference

compareAndSet() 方法源码:

public boolean compareAndSet(V   expectedReference,
                             V   newReference,
                             int expectedStamp,
                             int newStamp) {
    Pair<V> current = pair;
    return
        expectedReference == current.reference &&
        expectedStamp == current.stamp &&
        ((newReference == current.reference &&
          newStamp == current.stamp) ||
         casPair(current, Pair.of(newReference, newStamp)));
}

循环时间长

问题描述

因为CAS机制是: 一直进行失败重试, 直到成功为止, 那么自旋CAS 如果长时间不成功, 就会给 CPU 带来非常大的执行开销;

解决方案

只能保证一个共享变量的原子操作

问题描述

当对一个共享变量执行操作时,我们可以使用循环 CAS 的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性了

解决方案

总结

Java 中的 JUC

JUC包实现的示意图:

image
上一篇下一篇

猜你喜欢

热点阅读