什么是 CAS

发布时间:2024年01月24日

CAS(Compare And Swap,比较并交换),通常指的是这样一种原子操作:针对一个变量,首先比较它的内存值与某个期望值是否相同,如果相同,就给它赋一个新值。
CAS 的逻辑用伪代码描述如下:

if (value == expectedValue) {
    value = newValue;
}

以上伪代码描述了一个由比较和赋值两阶段组成的复合操作,CAS 可以看作是它们合并后的整体——一个不可分割的原子操作,并且其原子性是直接在硬件层面得到保障的。
CAS可以看做是乐观锁(对比数据库的悲观、乐观锁)的一种实现方式,Java原子类中的递增操作就通过CAS自旋实现的。

常见的乐观锁实现方式有两种,分别是:1、版本号机制;2、CAS算法。其中,通过版本号机制实现乐观锁是最经典的方法。版本号机制一般是在数据表中加上一个数据库版本号version字段。

/**
        * update 80  version:2 where version1 =数据库version2
        *UPDATE
        *     tbl_employee 
        * SET
        *     last_name='C',
        *     email='123@qq.com',
        *     gender=1,
        *     age=80,
        *     modify_date='2022-11-15 07:09:31.18',
        *     version=2 
        * WHERE
        *     id=2 
        *     AND version=1 
        *     AND delete_flag=0
        * */

CAS是一种无锁算法,在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步。

import java.util.concurrent.atomic.AtomicInteger;

//示例
public class VolatileVisibility2 {
    public static int i = 0;
    public static AtomicInteger integer = new AtomicInteger(0);
    public static Object lock = new Object();

    public static void increase() {
//        synchronized (lock){
//            i++;
//        }
        i++;
        integer.incrementAndGet();
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5000; i++) {
                    increase();
                }
            }
        },"t1");

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5000; i++) {
                    increase();
                }
            }
        },"t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();

        //思考: counter=?
        System.out.println("i = "+ i);
        System.out.println("integer = "+ integer.get());
    }
}

CAS应用

在 Java 中,CAS 操作是由 Unsafe 类提供支持的,该类定义了三种针对不同类型变量的 CAS 操作,如图
3b98ac6fcb163a5313452035ad731a8e_1642.png
它们都是 native 方法,由 Java 虚拟机提供具体实现,这意味着不同的 Java 虚拟机对它们的实现可能会略有不同。
以 compareAndSwapInt 为例,Unsafe 的 compareAndSwapInt 方法接收 4 个参数,分别是:对象实例、内存偏移量、字段期望值、字段新值。该方法会针对指定对象实例中的相应偏移量的字段执行 CAS 操作。

package com.cctv;

import sun.misc.Unsafe;

public class CASTest {

    public static void main(String[] args) {
        Entity entity = new Entity();

        Unsafe unsafe = UnsafeFactory.getUnsafe();

        long offset = UnsafeFactory.getFieldOffset(unsafe, Entity.class, "x");

        boolean successful;

        // 4个参数分别是:对象实例、字段的内存偏移量、字段期望值、字段新值
        successful = unsafe.compareAndSwapInt(entity, offset, 0, 3);
        System.out.println(successful + "\t" + entity.x);

        successful = unsafe.compareAndSwapInt(entity, offset, 3, 5);
        System.out.println(successful + "\t" + entity.x);

        successful = unsafe.compareAndSwapInt(entity, offset, 3, 8);
        System.out.println(successful + "\t" + entity.x);
    }
}

class Entity{
    int x;
}
package com.cctv;

import sun.misc.Unsafe;

import java.lang.reflect.Field;

public class UnsafeFactory {

    /**
     * 获取 Unsafe 对象
     *
     * @return
     */
    public static Unsafe getUnsafe() {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            return (Unsafe) field.get(null);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取字段的内存偏移量
     *
     * @param unsafe
     * @param clazz
     * @param fieldName
     * @return
     */
    public static long getFieldOffset(Unsafe unsafe, Class clazz, String fieldName) {
        try {
            return unsafe.objectFieldOffset(clazz.getDeclaredField(fieldName));
        } catch (NoSuchFieldException e) {
            throw new Error(e);
        }
    }
}

测试
针对 entity.x 的 3 次 CAS 操作,分别试图将它从 0 改成 3、从 3 改成 5、从 3 改成 8。执行结果如下:
db62c3486c262257e2e1eaaefd5d3f25_1643.png

CAS缺陷

CAS 虽然高效地解决了原子操作,但是还是存在一些缺陷的,主要表现在三个方面:

  • 自旋 + CAS 长时间地不成功,则会给 CPU 带来非常大的开销
  • 只能保证一个共享变量原子操作
  • ABA 问题

CAS + 自旋:
思考:这种CAS失败自旋的操作存在什么问题?

AtomicInteger atomicInteger = new AtomicInteger(1);
atomicInteger.incrementAndGet();

 public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

bfbcd548e059636bc98f2bb9e439810c_1656.png

文章来源:https://blog.csdn.net/qq_33240556/article/details/135741521
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。