并发编程(十六):原子类

1.无锁的原子类实现原理

1.1.CAS原理

原子类的无锁操作的实现很简单,那就是硬件支持,就是CPU提供的CAS指令(全称:Compare And Swap,即"比较-交换")。

CAS指令包含三个部分:

  1. 变量的内存地址A(当前值,curValue)
  2. 用于比较的值B(老值,expect)
  3. 共享变量的新值C(新值,newValue)

当前只有当内存地址A处的值等于B值时,才能够将内存地址A处的值更新为新值C。

作为一条CPU指令,CAS指令本身是能够保证原子性的

CAS的Java代码实现如下:

只有当目前 count 的值和期望值 expect 相等时,才会将 count 更新为 newValue

class SimulatedCAS{
  int count;
  synchronized int cas(
    int expect, int newValue){
    // 读目前 count 的值
    int curValue = count;
    // 比较目前 count 值是否 == 期望值
    if(curValue == expect){
      // 如果是,则更新 count 的值
      count = newValue;
    }
    // 返回写入前的值
    return curValue;
  }
}

1.2.CAS自旋

CAS自旋:其实就是循环尝试。就是说当前线程经过CAS比较发现expectVal与NowVal不符,代表数据已经被修改过了,此时就再次尝试读取新值计算,并再次CAS。代码如下:

class SimulatedCAS{
  volatile int count;
  // 实现 count+=1
  addOne(){
    do {
      newValue = count+1; //①
    }while(count !=
      cas(count,newValue) //②
  }
  // 模拟实现 CAS,仅用来帮助理解
  synchronized int cas(
    int expect, int newValue){
    // 读目前 count 的值
    int curValue = count;
    // 比较目前 count 值是否 == 期望值
    if(curValue == expect){
      // 如果是,则更新 count 的值
      count= newValue;
    }
    // 返回写入前的值
    return curValue;
  }
}

1.3.Java原子类如何通过CAS实现原子自增

这里以AtomicLong 的getAndIncrement()举例,其方法内部就是基于CAS实现,代码如下,其中this和valueoffset可以唯一确定共享变量的地址A:

final long getAndIncrement() {
  return unsafe.getAndAddLong(
    this, valueOffset, 1L);
}

unsafe.getAndAddLong() 方法的源码如下:

public final long getAndAddLong(
  Object o, long offset, long delta){
  long v;
  do {
    // 读取内存中的值
    v = getLongVolatile(o, offset);
  } while (!compareAndSwapLong(
      o, offset, v, v + delta));
  return v;
}
// 原子性地将变量更新为 x
// 条件是内存中的值等于 expected
// 更新成功则返回 true
native boolean compareAndSwapLong(
  Object o, long offset, 
  long expected,
  long x);

由源码可以看出,CAS的方法都是由上述的三个部分组成,成员变量地址A(当前值),期望值B,新值C

2.原子类概述

原子类可以大概划分为如下五个类别:

  1. 原子化的基本数据类型
  2. 原子化的对象的引用类型
  3. 原子化数组
  4. 原子化对象属性更新器
  5. 原子化的累加器

image-20210816213304898

2.1.原子化的基本数据类型

其中包括:AtomicBoolean、AtomicInteger 和 AtomicLong等

2.2.原子化的对象引用类型

其中包括:AtomicReference、AtomicStampedReference 和 AtomicMarkableReference,利用它们可以实现对象引用的原子化更新

AtomicStampedReference 和 AtomicMarkableReference 这两个原子类可以解决 ABA 问题

2.3.原子化数组

相关实现有 AtomicIntegerArray、AtomicLongArray 和 AtomicReferenceArray,利用这些原子类,我们可以原子化地更新数组里面的每一个元素

2.4.原子化对象属性更新器

相关实现有 AtomicIntegerFieldUpdater、AtomicLongFieldUpdater 和 AtomicReferenceFieldUpdater,利用它们可以原子化地更新对象的属性,这三个方法都是利用反射机制实现的,创建更新器的方法如下:

创建更新器的方法如下:

public static <U>
AtomicXXXFieldUpdater<U> 
newUpdater(Class<U> tclass, 
  String fieldName)

需要注意的是,对象属性必须是volatile类型的,只有这样才能保证可见性。如果对象属性不是 volatile 类型的,newUpdater() 方法会抛出 IllegalArgumentException 这个运行时异常。

2.5.原子化的累加器

DoubleAccumulator、DoubleAdder、LongAccumulator 和 LongAdder,这四个类仅仅用来执行累加操作,相比原子化的基本数据类型,速度更快,但是不支持 compareAndSet() 方法。如果你仅仅需要累加操作,使用原子化的累加器性能会更好。

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×