Java并发编程6——Atomic家族1

  • 概述
  • 基本类型(boolean, int, long, 对象)
    • AtomicBoolean
    • AtomicInteger
    • AtomicLong
    • AtomicReference
  • 数组(Long, Integer, 对象)
    • AtomicLongArray
    • AtomicIntegerArray
    • AtomicReferenceArray
  • 字段更新器(指定对象的指定字段)
    • AtomicLongFieldUpdater
    • AtomicIntegerFieldUpdater
    • AtomicReferenceFieldUpdater
  • 带版本号引用类型(对象,解决ABA)
    • AtomicStampedReference
    • AtomicMarkableReference

6.1 概述

  • 作用:这些类提供了 基于CAS的原子更新操作,适用于多线程之间共享变量
  • 包路径:java.util.concurrent.atomic
  • 类图
    • 这里写图片描述

6.2 基本类型:AtomicBoolean, AtomicInteger, AtomicLong, AtomicReference

(1)底层原理:volatile变量记录数字,调用底层Unsafe类对内存offset进行CAS操作

// AtomicInteger 和 AtomicBoolean
private volatile int value;

// AtomicLong
private volatile long value;

// AtomicReference
private volatile V value;

// AtomicInteger为例,判断当前值与expect是否一致,一致则update
public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

(2)API(以AtomicInteger为例)

构造器
  • AtomicInteger j = new AtomicInteger();    
  • AtomicInteger i = new AtomicInteger(0);  
注:无参构造默认为0
原子++和--
  • i.incrementAndGet();
  • j.getAndIncrement();
  • i.decrementAndGet();   
  • j.getAndDecrement();    
  • ++i, 返回1
  • j++, 返回0, 先获取
  • i--
  • --j
原子设置值
  • i.compareAndSet(pre, next)

通常与while一起使用

public static void updateAndGet(AtomicInteger i){
    while(true){
        int pre = i.get();
        int next = pre * 10;
        if(i.compareAndSet(pre, next)){
            break;
        }
    }
}
原子加减
  • i.getAndAdd(4);    
  • j.addAndGet(-5);  
  • 先获取,再+4

  • 先-5,再获取

获取
  • i.get()
  • 返回最新值
lambda函数
  • i.updateAndGet(函数);

  • i.getAndUpdate(函数);

  • eg.i.updataAndGet( x -> x*10)

6.3 数组类型:AtomicLongArray, AtomicIntegerArray, AtomicReferenceArray

(1)底层原理:volatile数组变量,需要注意的是,对于AtomicReferenceArray,保护的是数组中的值(用==比较两个Object),而并非数组中的引用(见下面代码注释)

//AtomicLongArray
private final long[] array;
//AtomicIntegerArray
private final int[] array;
//AtomicReferenceArray
private final Object[] array; // must have exact type Object[]
---------------------------------------------------------------------------
//AtomicReferenceArray
//Atomically sets the element at position i to the given updated value if the current value == the expected value.
//true if successful. False return indicates that the actual value was not equal to the expected value
public final boolean compareAndSet(int i, E expect, E update) {
    return compareAndSetRaw(checkedByteOffset(i), expect, update);
}

(2)API:略,与前者差不多

(3)使用例子

public class Test {
    public static void main(String[] args) {
        AtomicLongArray array = new AtomicLongArray(10);
        array.set(5,10); //对index=5设置值为10
        System.out.println("当前值: " + array.get(5));    //10
        Boolean bool = array.compareAndSet(5,10,30); //如果array[5]==10,原子更新为30
        System.out.println("结果值: " + array.get(5) + " Result: " + bool);    //30
    }
}

6.4 字段更新器

(1)用法(通过反射使用)

public class Test{
    //原子操作类
    private static AtomicLongFieldUpdater<Test> updater=AtomicLongFieldUpdater.newUpdater(Test.class,"count");
    //原子变量,使用volatile修饰,不能使用static修饰
    @Getter
    private volatile long count=100;

    public static void main(String[] args) {
        Test test=new Test();
        //如果Test中的count等于100,则更新count为120,返回true
        if (updater.compareAndSet(Test,100,120)){
            //输出更新后的count
            log.info("update success,count: {}",Test.getCount());
        }else {
            //更新失败,输出count
            log.info("update failed,count: {}",Test.getCount());
        }

        //此时count等于120,所以会返回false
        if (updater.compareAndSet(Test,100,120)){
            //输出更新后的count
            log.info("update success,count: {}",Test.getCount());
        }else {
            //更新失败,输出count
            log.info("update failed,count: {}",Test.getCount());
        }
    }
}

6.5 带版本号引用类型

(1)ABA问题

  • 线程1准备用CAS将变量的值由A替换为C,在此之前,线程2将变量的值由A替换为B,又由B替换为A,然后线程1执行CAS时发现变量的值仍然为A,所以CAS成功。但实际上这时的现场已经和最初不同了,尽管CAS成功,但可能存在潜藏的问题
  • AtomicReference会存在ABA问题,因此JUC提供了两个解决方法

(2)AtomicStampedReference:根据版本号判断是否存在ABA问题

  • 底层原理:把 Reference, Stamp打包成了静态内部类Pair<Reference, Integer>
private static class Pair<T> {
   final T reference;
   final int stamp;
   private Pair(T reference, int stamp) {
       this.reference = reference;
       this.stamp = stamp;
   }
   static <T> Pair<T> of(T reference, int stamp) {
       return new Pair<T>(reference, stamp);
   }
}   
  • 使用
//赋初值“A”,并设置版本号为0
static AtomicStampedReference<String> ref = new AtomicStampedReference<>("A",0);
 
public static void main(String args[]){
 
    String pre = ref.getReference();
    int stamp = ref.getStamp;
    
    //下面这行代码不仅判断pre=="A",还会判断 stamp与最新stamp是否相同,相同则更新版本号和“C”
    ref.compareAndSet(pre, "C", stamp, stamp +1)
}

(3)AtomicMarkableReference:有时候,并不关心变量被改了几次,只是关心变量是否被改过

  • 使用
AtmoicMarkableReference<String> ref = new AtomicMarkableReference<>("A", true);
 
String pre = ref.getReference();
 
//判断当前标记是否为true且当前值是否还是pre,如果都满足表示没有被改过,则更变为"C"和false
ref.compareAndSet(pre, "C", true, false);

猜你喜欢

转载自blog.csdn.net/qq_41157876/article/details/115331426