ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

一年工作经验的我,居然被问到了CAS

2022-01-24 12:59:52  阅读:161  来源: 互联网

标签:CAS Unsafe AtomicInteger 偏移量 对象 static value 问到 居然


楔子

前一篇文章,我们介绍了 synchronized,知道了 synchronized 可以解决某些数据的原子性问题,本篇文章我们以 AtomicInteger 为切入点,继续学习 CAS 无锁化的知识。

使用 synchronized 解决 i++的原子性

使用 synchronized 关键字,保证数据原子性

public class Test {

   static int synchronizedValue = 0;
   
   public static void main(String[] args) {
      // 使用 synchronized 的++
      synchronizedAdd();
   }
   
   private static void synchronizedAdd() {
      for(int i = 0; i < 50; i++) {
         new Thread(() -> {
            synchronized(Test.class) {
               System.out.println("synchronizedAdd:"+ ++Test.synchronizedValue);;
            }
         }).start();
      }
   }
    
}

结果:

synchronizedAdd:50

使用 AtomicInteger 解决自增原子性

static AtomicInteger atomicValue = new AtomicInteger(0);

public static void main(String[] args) throws InterruptedException {
   // 使用 atomic 的++
   atomicAdd();
}
private static void atomicAdd() {
   for(int i = 0; i < 50; i++) {
      new Thread(() -> System.out.println("atomicAdd:"+ Test.atomicValue.incrementAndGet())).start();
   }
}

结果

atomicAdd:50

CAS 原理

什么叫 CAS 呢?英文全称 compare and swap,翻译成中文就是比较和交换的意思。专业的说法叫乐观锁。说人话就是,我们修改数据的时候,会去尝试比较一下,这个值有没有被其他人修改过,如果没有被修改,那么我们自己就可以修改;如果已经被修改过了,那么我们就重新获取最新的值,重复执行以上步骤再次去比较。接下来我们从 AtomicInteger 源码进一步分析CAS。

AtomicInteger 源码解析

观察 AtomicInteger 源码,我们发现,他可以分为几个部分。

  1. Unsafe:核心类,真正负责执行 CAS 操作的类
  2. value 和 valueOffset:值与偏移量
  3. API 接口:对外提供各种使用方式,主要是封装了 Unsafe 的一些操作

Unsafe

Unsafe 顾名思义,这是一个不安全的类,内部是大量的 native 方法,JDK 原则上是不允许我们使用他的,他是供给 JDK 内部使用的一个类。首先他的构造函数是私有化,不能自己手动去实例化他, 其次,虽然他提供了 Unsafe.getUnsafe()方法来获取一个实例,但是他有一个判断,如果他不是由系统加载的他就会直接抛出异常,以上提到的源码如下:

// (1)构造方法私有化 
private Unsafe() {
}

public static Unsafe getUnsafe() {
    Class var0 = Reflection.getCallerClass();
    // (2)如果 Unsafe 不是由 JVM 加载的,那么就会报错
    if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
        throw new SecurityException("Unsafe");
    } else {
        return theUnsafe;
    }
}

valueOffset

类初始化的时候,会加载一个静态代码块,通过 unsafe 去确定一个 final 标记的偏移量,源码如下

private static final long valueOffset;

// 类初始化的时候,就会执行该静态代码块,通过 unsafe 去确定一个 final 标记的偏移量
static {
    try {
        valueOffset = unsafe.objectFieldOffset
            (AtomicInteger.class.getDeclaredField("value"));
    } catch (Exception ex) { throw new Error(ex); }
}

接着我们回到我们自己写的方法 atomicAdd

private static void atomicAdd() {
    for(int i = 0; i < 50; i++) {
        new Thread(() -> System.out.println("atomicAdd:"+ Test.atomicValue.incrementAndGet())).start();
    }
}

跟进 incrementAndGet 方法,这是一个包装方法,直接调用的 unsafe.getAndAddInt

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

跟进 unsafe.getAndAddInt 方法,好了以下,就是我们需要分析的代码了

public final int getAndAddInt(Object 当前对象, long 偏移量, int 自增值) {
    int 对象的 value;
    do {
        // 这个本地方法的作用是,
        // 从 AtomicInteger 对象实例,根据 valueOffset 偏移量,获取 value 这个字段的位置,从而获取到当前的 value 的值
        对象的 value/计算出的对象的 value = this.getIntVolatile(当前对象, 偏移量);
    } 
    // 如果【计算出的 value】和【当前对象+偏移量】的不一致,那么这一次的 compare 就为 false,那么就会进入下一次循环
    // 如果【计算出的 value】和【当前对象+偏移量】的一致,那么这一次的 compare 就为 true,此时
    // 【对象的 value】 = 【计算出的对象的 value】 + 【自增值】,并且跳出循环
    while(!this.compareAndSwapInt(当前对象, 偏移量, 计算出的对象的 value, 计算出的对象的 value + 自增值));

    return 对象的 value;
}

Atomic 原子类 CAS 常见的几种问题

ABA 问题

举个例子,比如当某个值为 A 的时候,你才进行操作。所以有可能会出现以下情况

  1. 初始为 A
  2. A——>B
  3. B——>A
  4. 开始操作
    (2)到(3)这个步骤,这个值是被人改过的,但是这个值和我期望的值是一样的,所以我们去 compareAndSwapInt 的时候,会发现这个值还是 A,就设置成功了。

注:如何解决 ABA 问题呢?加个时间戳就搞定了,比较的时候带上时间戳。

无限循环问题

看源码,因为我们是走的 do…while循环,所以有可能会循环很多次,都比较不成功。

注:如何解决无限循环问题呢?jdk 给我们提供了一个思路,分段 CAS,感兴趣的读者可以参考 LongAdder 类。

自定义对象原子问题

AtomicInteger,只能保证一个变量的原子性。

注:复杂对象怎么办呢?jdk 给我们提供了 AtomicReference,他比较的是这个对象的引用是不是一个。

标签:CAS,Unsafe,AtomicInteger,偏移量,对象,static,value,问到,居然
来源: https://blog.csdn.net/a18602320276/article/details/122665214

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有