Java多线程之原子操作类

111 阅读5分钟

通过CAS机制来实现对于某一个字段某一个数组等进行原子操作,保证是线程安全的,例如i++的线程安全也可以通过原子操作类来实现,何为CAS我们最后讲

原子更新基本类型类

  • AtomicBoolean
  • AtomicInteger
  • AtomicLong
方法解释
int addAndGet(int delta)以原子的方式将输入的数值与实例中的值相加,并返回结果
boolean compareAndSet(int expect,int update)如果输入的数值等于预期值,则以原子方式将该值设置为输入的值
int getAndIncrement()以原子的方式将当前值加1,返回自增之前的值
void lazySet(int newValue)最终会设置为newValue,使用lazySet设置值后,可能导致其他线程在之后的一小段时间内还是可以读到旧的值
int getAndSet(int newValue)以原子方式设置为newValue的值,并返回旧值
//AtomicInteger
@Test
public void testAtomicInteger() throws InterruptedException {
    AtomicInteger atomicInteger = new AtomicInteger(1);
    int iag = atomicInteger.incrementAndGet();
    System.out.println("incrementAndGet的结果为:"+iag);
    atomicInteger.compareAndSet(2,1);
    System.out.println("compareAndSet后的结果是:"+atomicInteger.get());

}

image.png

public final int getAndIncrement() {
    for (;;) {
        int current = get();  // 获取当前值
        int next = current + 1;  // 计算下一个值
        if (compareAndSet(current, next))  // 使用CAS操作尝试更新值
            return current;  // 返回旧值
    }
}
 public final boolean compareAndSet(int expect, int update) { 
      //如果期望值 等于目前值,那么就能进行修改
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

其他两个也是同样的道理,但在Unsafe类中,只提供了3种CAS方法,而对于Boolean类型的操作,是先把Boolean转换成整型,在使用compareAndSwapInt进行CAS

原子更新数组

  • AtomicIntegerArray:原子更新整型数组里的元素
  • AtomicLongArray:原子更新长整型数组里的元素
  • AtomicReferenceArray:源自更新引用类型数组里的元素
方法解释
int addAndGet(int i,int delta)以原子方式将输入值于数组中索引i的元素相加
boolean compareAndSet(int i ,int expect,int update)如果当前值等于预期值,则以原子的方式进行队索引i元素的修改。

数组value通过构造方法传递进去,然后AtomicIntegerArray会将当前数组复制一份,所以当AtomicIntegerArray对内部的数组元素进行修改时,不会影响传入的数组。

@Test
public void testAtomicIntegerArray(){
    int[] arr = new int[]{1,2,3,4};
    AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(arr);
    atomicIntegerArray.compareAndSet(2,3,1);
    for (int i = 0; i < arr.length; i++) {
        System.out.print(arr[i]);
    }

}

image.png

@Test
public void testAtomicIntegerArray(){
    int[] arr = new int[]{1,2,3,4};
    AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(arr);
    atomicIntegerArray.compareAndSet(2,3,1);
    //经过修改后的arr数组
    System.out.print("经过修改后的arr数组:");
    for (int i = 0; i < arr.length; i++) {
        System.out.print(arr[i]);
    }
    System.out.println();
    System.out.print("获取修改后AtomicIntegerArray数组:");
    //获取修改后AtomicIntegerArray数组
    for (int i = 0; i < arr.length; i++) {
        System.out.print(atomicIntegerArray.get(i));
    }
}

image.png

说明这两个数组并不是同一个

原子更新引用类型

  • AtomicReference:原子更新引用类型
  • AtomicReferenceFieldUpdate:原子更新引用类型里的字段
  • AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子更新一个布尔类型的标记位和引用类型

原子更新字段类

如果需要原子的更新某个类的某个字段,那么就可以使用原子更新字段类

  • AtomicIntegerFieldUpdate:原子更新整型的字段的更新器。
  • AtomicLongFieldUpdate:原子更新长整型字段的更新器。
  • AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更新数据和数据的版本号,可以解决使用CAS进行原子更新时可能出现的ABA问题。

CAS

CAS 全称 Compare And Swap(比较与交换),是一种用于在多线程环境下实现同步功能的机制。在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步。java.util.concurrent 包中的原子类就是通过 CAS 来实现了乐观锁。

CAS的具体实现

  • CAS操作包含三个操作数:需要更新的内存值V、进行比较的预期数值A和要写入的值B。
  • CAS的实现逻辑,是将需要更新的内存值V和预期值A进行比较,当且仅当V的值等于A的时候,CAS通过原子方式用新值B来更新V的值("比较+更新"整体是一个原子操作),否则不会执行任何操作。
  • 一般情况下,"更新"是一个不断重试的操作。
  • 我们以java.util.concurrent.atomic 并发包下 AtomicInteger 原子整型类为例,一起来分析下 CAS 底层实现机制。
  • atomicData.incrementAndGet();
public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

//Unsafe类中的方法
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;
}

从上面的代码可以见到AtomicInteger的内部方法都是基于Unsafe类实现的,Unsafe类是一个底层的指令复制工具类,主要看这个compareAndSet()方法的返回:

unsafe.compareAndSwapInt(this, valueOffset, expect, update);
  • this就是Unsafe对象本身,需要通过这个类来获取value的内存偏移地址
  • valueOffset,value变量的内存偏移地址
private static final long valueOffset;

static {
    try {
        valueOffset = unsafe.objectFieldOffset
            (AtomicInteger.class.getDeclaredField("value"));
    } catch (Exception ex) { throw new Error(ex); }
}
  • expect,期望更新的值
  • update,要更新的最新值

如果原子变量中的 value 值等于 expect,则使用 update 值更新该值并返回 true,否则返回 false。

private volatile int value;

/**
 * Creates a new AtomicInteger with the given initial value.
 *
 * @param initialValue the initial value
 */
public AtomicInteger(int initialValue) {
    value = initialValue;
}

这里的value实际变量,是由volatile关键字修饰的,为了保证在多线程下内存的可见性

CAS的底层Unsafe类

[juejin.cn/post/706644…]