学习多并发第二课--原子类

JAVA从JDK 1.5之后开始在JUC提供了atomic包,一共有17个类用于原子更新数据,分为四种类型:原子更新基本类型原子更新引用类型原子更新属性原子更新数组

//原子更新对象
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
//原子更新整型
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
//原子更新长整型
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

atomic中17个类的方法都是基于Unsafe中的三个CAS方法实现的,Unsafe中有三个原子操作的CAS方法(比较和替换)。

原子更新基本类型

包括三个类:

  • AtomicInteger:原子更新整型
  • AtomicLong:原子更新长整型
  • AtomicBoolean:原子更新布尔型

AtomicInteger

属性

    private volatile int value;//用volatile修饰的变量存储值

getAndAdd() 获取到当前的值,并加上想加的值

//AtomiceInterger中的getAndAdd()
public final int getAndAdd(int var1) {
        return unsafe.getAndAddInt(this, valueOffset, var1);//返回的是改变之前的值
}
//Unsafe中的getAndAddInt()
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));//才用自旋CAS改变值,值为当前值+想改变的值

        return var5;//返回改变前的值
 }
//Unsafe中的getIntVolatiele
 public native int getIntVolatile(Object var1, long var2);

addAndGet() 加上想加的值,并获取最后的值

    public final int addAndGet(int var1) {
        return unsafe.getAndAddInt(this, valueOffset, var1) + var1;//返回改变后的值
    }
    //Unsafe中的getAndAddInt()
    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));//才用自旋CAS改变值,值为当前值+想改变的值
    
            return var5;//返回改变前的值
     }
    //Unsafe中的getIntVolatiele
     public native int getIntVolatile(Object var1, long var2);

getAndIncrement() 获取当前的值并+1 i++

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

incrementAndGet() +1并获取加成的值 ++i

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

compareAndSet() 调用unsafe中的CAS方法改变值

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

AtomicBoolean

//属性
private volatile int value; //使用了一个valatile修饰的整型变量来存储布尔值 1代表true 0代表false 

//方法 compareAndSet(boolean expect, boolean update) 比较和改变布尔值
//如果成功返回true,失败表示现在的值不是预期值
public final boolean compareAndSet(boolean expect, boolean update) {
    int e = expect ? 1 : 0;//将期望的布尔值变成整型
    int u = update ? 1 : 0;//将期望的布尔值变成整型
    return unsafe.compareAndSwapInt(this, valueOffset, e, u);//调用Unsafe的CAS方法改变布尔值
}

//方法 getAndSet(boolean newValue) 获取和改变布尔值
public final boolean getAndSet(boolean newValue) {
    boolean prev;
    do {
        prev = get();//获取当前的布尔值
    } while (!compareAndSet(prev, newValue));//采用自旋+调用compareAndSet()改变值
    return prev;//返回改变前的布尔值
}


原子更新引用类型

包括3个类:

  • AtomicReference:原子更新引用类型;
  • AtomicStampedReference:原子更新带有标记位的引用类型;
  • AtomicMarkableReference:原子更新带有标记位的引用类型。

其中,后两个类用于解决ABA问题,区别是标记位的类型不同,它们分别以整数/布尔类型做标记位。

AtomicReference

//属性
private volatile V value;//用volatile修饰的泛型变量来存储数据

//compareAndSet(V expect, V update) 调用Unsafe的CAS方法
//如果成功返回true,失败返回false
public final boolean compareAndSet(V expect, V update) {
    return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}


//getAndSet(V newValue) 获取当前的值并改成新值
public final V getAndSet(V newValue) {
    return (V)unsafe.getAndSetObject(this, valueOffset, newValue);
}
//Unsafe的getAndSetObject(Object var1, long var2, Object var4)
public final Object getAndSetObject(Object var1, long var2, Object var4) {
    Object var5;
    do {
        var5 = this.getObjectVolatile(var1, var2);
    } while(!this.compareAndSwapObject(var1, var2, var5, var4));

    return var5;
}

//getAndUpdate(UnaryOperator<V> updateFunction) 尝试更改引用值,值尝试一次,不会一直自旋占用资源 返回更改之前的值
public final V getAndUpdate(UnaryOperator<V> updateFunction) {
    V prev, next;
    do {
        prev = get();
        next = updateFunction.apply(prev);
    } while (!compareAndSet(prev, next));
    return prev;
}
//updateAndGet(UnaryOperator<V> updateFunction) 尝试更改引用值,值尝试一次,不会一直自旋占用资源 返回更改之前的值
public final V updateAndGet(UnaryOperator<V> updateFunction) {
    V prev, next;
    do {
        prev = get();
        next = updateFunction.apply(prev);
    } while (!compareAndSet(prev, next));
    return next;
}

AtomicStampedReference

的//有一个内部类Pair reference用来存储值  stamp存储版本号
private static class Pair<T> {
    final T reference;
    final int stamp;
    private Pair(T reference, int stamp) {
        this.reference = reference;
        this.stamp = stamp;
}

//定义一个用Volatile修饰的Pair 来存储值和版本号
private volatile Pair<V> pair;

//compareAndSet(V  expectedReference,V   newReference,int expectedStamp,int newStamp)
public boolean compareAndSet(V   expectedReference,
                             V   newReference,
                             int expectedStamp,
                             int newStamp) {
    Pair<V> current = pair;
    return
        expectedReference == current.reference && //检查现在的值和期望的是否一样
        expectedStamp == current.stamp &&//检查现在的版本和期望的是否一样
        ((newReference == current.reference &&//如果新的值和现在值一样或者新的版本号和现在一样则不修改
          newStamp == current.stamp) ||
         casPair(current, Pair.of(newReference, newStamp)));//新的值和现在不一样且版本号不一样调用casPair()修改值
}

//casPair(Pair<V> cmp, Pair<V> val) 调用Unsafe中的CAS方法
private boolean casPair(Pair<V> cmp, Pair<V> val) {
    return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
}

//attemptStamp(V expectedReference, int newStamp) 只更改版本号
public boolean attemptStamp(V expectedReference, int newStamp) {
    Pair<V> current = pair;
    return
        expectedReference == current.reference && //检测期望的值是不是等于现在的值
        (newStamp == current.stamp || //检测新的版本号和现在版本号是不是相等
         casPair(current, Pair.of(expectedReference, newStamp)));//当期望的值和现在的值一样且版本号不一样时,更改版本号
}

原子更新属性

原子更新数组

Striped64

从JDK 8开始,Java提供了Striped64类,该类共包含4个子类,分别是LongAdder、LongAccumulator、DoubleAdder、DoubleAccumulator。之所以提供这些类,是为了解决在高并发场景下多个线程对同一个变量进行CAS操作的性能问题

Striped64

//内部类Cell 帮助存数据
@sun.misc.Contended static final class Cell {
    volatile long value;//存储值
    Cell(long x) { value = x; }
    final boolean cas(long cmp, long val) { // CAS修改值
        return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
    }
}
//定义了一个cell数组 细胞表用来帮助存储数据  大小为2的次幂
transient volatile Cell[] cells;
//基本值 
transient volatile long base;
//锁状态
transient volatile int cellsBusy;

//CAS更换基础值
final boolean casBase(long cmp, long val) {
    return UNSAFE.compareAndSwapLong(this, BASE, cmp, val);
}
//CAS获取锁
final boolean casCellsBusy() {
    return UNSAFE.compareAndSwapInt(this, CELLSBUSY, 0, 1);
}
//CAS处理cells
final void longAccumulate(long x, LongBinaryOperator fn,boolean wasUncontended)

LongAdder

//add方法
public void add(long x) {
    Cell[] as; long b, v; int m; Cell a;
    if ((as = cells) != null || !casBase(b = base, b + x)) {//如果cells细胞表为空的,则直接在base基础值上添加
        boolean uncontended = true;
        if (as == null || (m = as.length - 1) < 0 ||//
            (a = as[getProbe() & m]) == null ||
            !(uncontended = a.cas(v = a.value, v + x)))
            longAccumulate(x, null, uncontended);
    }
}
//+1
public void increment() {
  add(1L);
}
//-1
public void decrement() {
  add(-1L);
}
//sun方法 把base和cells值加到一起
public long sum() {
Cell[] as = cells; Cell a;
long sum = base;
if (as != null) {
    for (int i = 0; i < as.length; ++i) {
        if ((a = as[i]) != null)
            sum += a.value;
    }
}
return sum;
}
//将base和cells的值置为0
public void reset() {
Cell[] as = cells; Cell a;
base = 0L;
if (as != null) {
    for (int i = 0; i < as.length; ++i) {
        if ((a = as[i]) != null)
            a.value = 0L;
    }
}
}
//将base和cells的添加到一块并返回,然后将数据置为0
public long sumThenReset() {
    Cell[] as = cells; Cell a;
    long sum = base;
    base = 0L;
    if (as != null) {
        for (int i = 0; i < as.length; ++i) {
            if ((a = as[i]) != null) {
                sum += a.value;
                a.value = 0L;
            }
        }
    }
    return sum;
}


原子操作的三大问题

ABA问题

ABA问题值得就是一个变量A→B→A,CAS检查的时候是没有变的,但是其实已经变了。 ABA问题的解决思路是给变量提供版本号 atomic包中提供AtomicStampedReference和AtomicMarkableReference来解决这类问题

自旋开销大

原子类的CAS操作通常伴有自旋,对CPU的消耗比较大

只能保证一个变量的原子操作

对多个共享变量操作时就无法保证原子操作。可以考虑把多个变量组合成一个共享变量,然后采用原子更新引用类型的方式,或者也可以加锁

全部评论

相关推荐

01-21 12:26
暨南大学 golang
点赞 评论 收藏
分享
评论
1
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务