学习多并发第二课--原子类
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的消耗比较大
只能保证一个变量的原子操作
对多个共享变量操作时就无法保证原子操作。可以考虑把多个变量组合成一个共享变量,然后采用原子更新引用类型的方式,或者也可以加锁