前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java并发——CAS(十)

Java并发——CAS(十)

原创
作者头像
翰墨飘香
发布2024-05-21 23:39:06
730
发布2024-05-21 23:39:06
举报
文章被收录于专栏:Java并发Java并发

一、概述

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 原子更新器

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、概述
    • 1.1 CAS介绍
      • 1.2 CAS思路
        • 1.3 CAS优缺点
          • CAS优点
          • CAS缺点
      • 二、CAS使用
        • 2.1 原子类的CAS
          • 2.2 并发容器的CAS
            • 2.3 数据库的CAS
            • 三、原子类
              • 3.1 原子类作用
                • 3.2 原子类详述
                  • 3.2.1 Atomic基本类型原子类
                  • 3.2.2 Array 数组类型原子类
                  • 3.2.3 Atomic Reference 引用类型原子类
                  • 3.2.4 AtomicFieldUpdater 原子更新器
              相关产品与服务
              容器服务
              腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档