Java并发——CAS(十)

2024-05-21 23:39:06 浏览数 (1)

一、概述

1.1 CAS介绍

CAS全称是 Compare-And-Swap 意思为比较并替换,主要用于乐观锁

CAS分两步:

1、比较: 读取到了一个值 A,在将其更新为 B 之前,检查原值是否仍为 A

2、设置: 检查结果为真,则更新为B,否则一直重试,直到成功为止

深入理解 CAS 原理 | Java

1.2 CAS思路

在大多数处理器的指令中,都会实现 CAS 相关的指令,这一条指令就可以完成“比较并交换”的操作,也正是由于这是一条(而不是多条)CPU 指令,所以 CAS 相关的指令是具备原子性的,这个组合操作在执行期间不会被打断,这样就能保证并发安全。由于这个原子性是由 CPU 保证的,所以无需我们程序员来操心。

CAS 有三个操作数:内存值 V、预期值 A、要修改的值 B。CAS 最核心的思路就是,仅当预期值 A 和当前的内存值 V 相同时,才将内存值修改为 B。

1.3 CAS优缺点

CAS优点

可以避免加互斥锁,可以提高程序的运行效率

CAS缺点

ABA 问题

CAS 有一个典型的问题就是ABA问题,即原始值最初为a,但是中间被其它线程修改多次,最后又变为了a,当进行比较时,我们程序认为没有被其它线程修改。CAS 并不能检测出在此期间值是不是被修改过,它只能检查出现在的值和最初的值是不是一样。

ABA 问题对中间一定不能被其它线程操作的场景有影响,可以通过版本号的方式解决,因此,使用时需要注意是否需要规避ABA问题。

自旋时间过长

由于单次 CAS 不一定能执行成功,所以 CAS 往往是配合着循环来实现的,有的时候甚至是死循环,不停地进行重试,直到线程竞争不激烈的时候,才能修改成功。

可是如果我们的应用场景本身就是高并发的场景,就有可能导致 CAS 一直都操作不成功,这样的话,循环时间就会越来越长。而且在此期间,CPU 资源也是一直在被消耗的,这会对性能产生很大的影响。所以这就要求我们,要根据实际情况来选择是否使用 CAS,在高并发的场景下,通常 CAS 的效率是不高的。

范围不能灵活控制

不能针对多个共享变量同时进行 CAS 操作,因为这多个变量之间是独立的,简单的把原子操作组合到一起,并不具备原子性。因此如果我们想对多个对象同时进行 CAS 操作并想保证线程安全的话,是比较困难的

有一个解决方案,那就是利用一个新的类,来整合刚才这一组共享变量,这个新的类中的多个成员变量就是刚才的那多个共享变量,然后再利用 atomic 包中的 AtomicReference 来把这个新对象整体进行 CAS 操作,这样就可以保证线程安全。

相比之下,如果我们使用其他的线程安全技术,那么调整线程安全的范围就可能变得非常容易,比如我们用 synchronized 关键字时,如果想把更多的代码加锁,那么只需要把更多的代码放到同步代码块里面就可以了

二、CAS使用

2.1 原子类的CAS

java的原子类位于java.util.concurrent.atomic包下

AtomicInteger 使用了CAS,例如getAndAdd方法

代码语言:java复制
public final int getAndAdd(int delta) {    
    return unsafe.getAndAddInt(this, valueOffset, delta);
}
代码语言:java复制
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;
    }

2.2 并发容器的CAS

ConcurrentHashMap 的putVal中使用了casTabAt方法

代码语言:java复制
final V putVal(K key, V value, boolean onlyIfAbsent) {

    if (key == null || value == null) throw new NullPointerException();

    int hash = spread(key.hashCode());

    int binCount = 0;

    for (Node<K,V>[] tab = table;;) {

        Node<K,V> f; int n, i, fh;

        if (tab == null || (n = tab.length) == 0)

            tab = initTable();

        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {

            if (casTabAt(tab, i, null,

                         new Node<K,V>(hash, key, value, null)))

                break;                   // no lock when adding to empty bin

        }

    //以下部分省略

    ...

    static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,

                                    Node<K,V> c, Node<K,V> v) {

    return U.compareAndSwapObject(tab, ((long)i << ASHIFT)   ABASE, c, v);

}

}

2.3 数据库的CAS

在我们的数据库中,也存在对乐观锁和 CAS 思想的应用。在更新数据时,我们可以利用 version 字段在数据库中实现乐观锁和 CAS 操作,而在获取和修改数据时都不需要加悲观锁。

具体思路如下:当我们获取完数据,并计算完毕,准备更新数据时,会检查现在的版本号与之前获取数据时的版本号是否一致,如果一致就说明在计算期间数据没有被更新过,可以直接更新本次数据;如果版本号不一致,则说明计算期间已经有其他线程修改过这个数据了,那就可以选择重新获取数据,重新计算,然后再次尝试更新数据。

假设取出数据的时候 version 版本为 1,相应的 SQL 语句示例如下所示:

代码语言:sql复制
UPDATE order  SET name = 'aaa' Where version = 2  

三、原子类

3.1 原子类作用

原子性意味着“要么全部成功,要么全部失败” java的原子类位于java.util.concurrent.atomic包下,作用和锁类似,和锁相比优势有:

粒度更细

原子变量可以把竞争范围缩小到变量级别,通常情况下,锁的粒度都要大于原子变量的粒度。

效率更高

除了高度竞争的情况之外,使用原子类的效率通常会比使用同步互斥锁的效率更高,因为原子类底层利用了 CAS 操作,不会阻塞线程。

3.2 原子类详述

原子类包含:

类型

具体类

Atomic* 基本类型原子类

AtomicInteger、AtomicLong、AtomicBoolean

Atomic*Array 数组类型原子类

AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray

Atomic*Reference 引用类型原子类

AtomicReference、AtomicStampedReference、AtomicMarkableReference

Atomic*FieldUpdater 升级类型原子类

AtomicIntegerfieldupdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater

Adder 累加器

LongAdder、DoubleAdder

Accumulator 积累器

LongAccumulator、DoubleAccumulator

3.2.1 Atomic基本类型原子类

包含AtomicInteger、AtomicLong、AtomicBoolean

以AtomicInteger为例子,它是对于 int 类型的封装,并且提供了原子性的访问和更新。也就是说,我们如果需要一个整型的变量,并且这个变量会被运用在并发场景之下,我们可以不用基本类型 int,也不使用包装类型 Integer,而是直接使用 AtomicInteger,这样一来就自动具备了原子能力,使用起来非常方便。

1、源码

AtomicInteger源码

代码语言:java复制
public class AtomicInteger extends Number implements java.io.Serializable {
    /*
     * This class intended to be implemented using VarHandles, but there
     * are unresolved cyclic startup dependencies.
     */
    private static final Unsafe U = Unsafe.getUnsafe();
    private static final long VALUE
        = U.objectFieldOffset(AtomicInteger.class, "value");

    private volatile int value;

    //获取当前的值
    public final int get() {
        return value;
    }

    //设置值
    public final void set(int newValue) {
        value = newValue;
    }

    //获取当前值,并设置新值
     public final int getAndSet(int newValue) {
        return U.getAndSetInt(this, VALUE, newValue);
    }
     
    //如果输入的数值等于预期值,则以原子方式将该值更新为输入值(update)
    public final boolean compareAndSet(int expectedValue, int newValue) {
        return U.compareAndSetInt(this, VALUE, expectedValue, newValue);
    }

    //获取当前的值,并自增
    public final int getAndIncrement() {
        return U.getAndAddInt(this, VALUE, 1);
    }

    //获取当前的值,并自减
    public final int getAndDecrement() {
        return U.getAndAddInt(this, VALUE, -1);
    }

    //获取当前的值,并加上预期的值
    public final int getAndAdd(int delta) {
        return U.getAndAddInt(this, VALUE, delta);
    }

Unsafe类中部分代码

代码语言:java复制
@IntrinsicCandidate
    public final int getAndSetInt(Object o, long offset, int newValue) {
        int v;
        do {
            v = getIntVolatile(o, offset);
        } while (!weakCompareAndSetInt(o, offset, v, newValue));
        return v;
    }

2、使用例子

代码语言:java复制
 @Test
    public void AtomicIntegerDemo() {
        AtomicInteger atomicInteger = new AtomicInteger(0);
        ExecutorService executorService = Executors.newFixedThreadPool(8);
        for (int i = 1; i <= 100; i  ) {
            executorService.execute(() -> {
                atomicInteger.getAndIncrement();
                System.out.println(atomicInteger.get());
            });
        }
        System.out.println("final:"   atomicInteger.get());
    }

3.2.2 Array 数组类型原子类

3.2.3 Atomic Reference 引用类型原子类

3.2.4 AtomicFieldUpdater 原子更新器

0 人点赞