【并发包】Atomic相关
1. 统一API一览
注意:
- oldValue指方法内部在执行操作前,通过get方法获取得值,而newValue则是已经执行操作后的值
- 一般get在前面,说明返回的是旧值否则为新值。
方法 | 参数 | 返回 | 详细解释 |
---|---|---|---|
set | newValue | void | validate式设置 |
lazySet | newValue | void | 赋值式设置(其他线程不一定可见) |
getAndSet | newValue | oldValue | validate式设置 |
compareAndSet | expect,update | boolean | 比较expect,如相同则设置并返回true,如不同则不设置并返回false |
weakCompareAndSet | expect,update | boolean | |
getAndIncrement | void | oldValue | 原子++ |
getAndDecrement | void | oldValue | 原子-- |
getAndAdd | delta | oldValue | 原子+delta |
incrementAndGet | void | newValue | 原子++ |
decrementAndGet | void | newValue | 原子-- |
addAndGet | delta | newValue | 原子+delta |
getAndUpdate | xxxUnaryOperator (FunctionalInterface ,一个参数一个返回结果) | oldValue | 把oldValue给xxxUnaryOperator |
updateAndGet | xxxUnaryOperator (FunctionalInterface ,一个参数一个返回结果) | newValue | 把oldValue给xxxUnaryOperator得到返回值并返回 |
getAndAccumulate | x,xxxBinaryOperator | oldValue | 把x和oldValue给xxxBinaryOperator |
accumulateAndGet | x,xxxBinaryOperator | newValue | 把x和oldValue给xxxBinaryOperator得到返回值并返回 |
2 AtomicInt (细讲)
2.1一些变量
unsafe :每个atomic类基本都必须使用这个类。
valueOffset:value这个变量在atomicInt这个类的内存上的偏移值。
value:当前对象的值。
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
//通过字节码,拿到value字段相较于整个atomic对象的偏移值。
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
2.2 set/lazySet
//更新值时,因为value是volatile,所以其他线程立刻可见。
public final void set(int newValue) {
value = newValue;
}
// 更新值时,不保证其他线程可见。相对于set应该有性能上优势。
public final void lazySet(int newValue) {
unsafe.putOrderedInt(this, valueOffset, newValue);
}
2.3 getAndSet
public final int getAndSet(int newValue) {//返回上一次的值,并set新的value
return unsafe.getAndSetInt(this, valueOffset, newValue);
}
public final int getAndSetInt(Object o, long offset, int newValue) {
int v;
do {
v = getIntVolatile(o, offset);//通过偏移量拿到value
} while (!compareAndSwapInt(o, offset, v, newValue));//如果这次value和上次value不同,那么返回false
return v;
}
2.4 compareAndSet、weakCompareAndSet
在java9之前,这个两个方法基本一样,我们尽量用compareAndSet。
//比较expect是否与value值一直,如果不一致,就返回false,并不更新。
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
public final boolean weakCompareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
2.5 unsafe.getAndAddInt 相关的方法
先看 getAndAddInt 方法源码
//可以看出这个方法,就是原子性对value加上一个delta,并返回原值
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);//通过偏移量拿到值
} while (!compareAndSwapInt(o, offset, v, v + delta));//原子性对值+delta。
return v;
}
在看atomic相关方法:
//对value+1并返回原值
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
//对value-1并返回原值
public final int getAndDecrement() {
return unsafe.getAndAddInt(this, valueOffset, -1);
}
//对value加上一个delta并返回原值
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
//对value+1并返回新值
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
//对value-1并返回新值
public final int decrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
}
//对value加上一个delta并返回新值
public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}
2.6 getAndUpdate、updateAndGet、getAndAccumulate、accumulateAndGet
//将旧值传给updateFunction得到新值,然后比较更新,如失败则重复循环,直至成功返回旧值
public final int getAndUpdate(IntUnaryOperator updateFunction) {
int prev, next;
do {
prev = get();
next = updateFunction.applyAsInt(prev);
} while (!compareAndSet(prev, next));
return prev;
}
//将旧值传给updateFunction得到新值,然后比较更新,如失败则重复循环,直至成功返回新值
public final int updateAndGet(IntUnaryOperator updateFunction) {
int prev, next;
do {
prev = get();
next = updateFunction.applyAsInt(prev);
} while (!compareAndSet(prev, next));
return next;
}
//将旧值和x传给accumulatorFunction得到新值,然后比较更新,如失败则重复循环,直至成功返回旧值
public final int getAndAccumulate(int x,
IntBinaryOperator accumulatorFunction) {
int prev, next;
do {
prev = get();
next = accumulatorFunction.applyAsInt(prev, x);
} while (!compareAndSet(prev, next));
return prev;
}
//将旧值和x传给accumulatorFunction得到新值,然后比较更新,如失败则重复循环,直至成功返回新值
public final int accumulateAndGet(int x,
IntBinaryOperator accumulatorFunction) {
int prev, next;
do {
prev = get();
next = accumulatorFunction.applyAsInt(prev, x);
} while (!compareAndSet(prev, next));
return next;
}
2.7 总结 AtomicInt
1.如果前面是getxxx,那么更新后返回旧值,否则就返回更新后的新值。
2.核心方法,都是依靠compareAndSet
3. AtomicBoolean
value是通过int来实现,几乎和AtomicInteger一模一样,其中0表示false,而1表示true。
private volatile int value;
public AtomicBoolean(boolean initialValue) {
value = initialValue ? 1 : 0;
}
4. AtomicLong
由于AtomicLong方法基本和前面AtomicInt一致所以不细写Api分析了。
仅仅写下唯一差别。
static final boolean VM_SUPPORTS_LONG_CAS = VMSupportsCS8();
private static native boolean VMSupportsCS8();
这些事给jvm来调用的。
有些cpu是32位的数据总线一次就只能传32个字节,long是64个字节,可能需要传两次,这样就不能保持原子性。所以需要加锁。
如果JVM的long操作是原子化的,会采用无锁的CAS来更新,如果不支持就会使用带锁的方式来更新。
所以尽量使用int类型。如果一定要用long类型,尽量保证cpu是64位的,系统是64位的。
5. AtomicReference
和前面的不同,AtomicReference 提供了对Obeject类型的compareAndSet,当然compare的是java引用
API和前面的atomicInteger基本一模一样(当然原子++--都没有了)。
6. ABA问题和AtomicStampedReference
6.1 ABA问题引入。
线程要修改变量值:
线程t1: a->c
线程t2: a->b->a
t1先拿到变量值为a,然后t2执行a->b->a,此时线程t1 进行CAS发现变量值还是a,所以将a->c。
对比sychronized,如果使用sychronized,线程t2就无法对变量进行修改。
而使用CAS则会导致,线程t1仅仅只能比较变量值是否相等,而无法知道之前是否已经发生变化了。
这其实和挪用资产挺像的,比如小张挪用100W公司资产炒股,赚了10W,让后把100W补回公司,这样公司查账后发现资产没变化,但是小张多了10W
6.2 AtomicStampedReference
可以看出来CAS其实就好像乐观锁一样,因此我们可以参考一下SVN的解决方案加上version。
而AtomicStampedReference中的Stamped就是类似version的做法。
6.2.1 Pair
Pair可以说是唯一个和AtomicReference 类的区别。
Pair的作用就是给value,也就是给 T refresh绑定了一个时间搓。返回一个新类叫pair。
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);
}
}
private volatile Pair<V> pair;
public AtomicStampedReference(V initialRef, int initialStamp) {
pair = Pair.of(initialRef, initialStamp);
}
6.2.2 compareAndSet
就写法而言还是有点炫酷,类似js的写法,如果是A&&B那么A为true会执行B,如果是A||B,那么A为false则会执行B。
- 这里先比较传进来的expectedReference和expectedStamp是否和当前pair一致,如果不一致返回false。
- 再比较传进来的newReference和newStamp是否和pair一一致,如果一一致则直接返回true。
- 如果不一致,则进行CAS后返回。
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair<V> current = pair;
return
expectedReference == current.reference && expectedStamp == current.stamp //1
&& (
(newReference == current.reference && newStamp == current.stamp) //2
|| casPair(current, Pair.of(newReference, newStamp))//3
);
}
7.AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
作用:对数组内部的元素进行原子操作。
我们以AtomicIntegerArray为例:
7.1 int数组内存中结构
可以看出主要由两部分构成:header ,int 元素
其中每个int元素为4个字节,而header则是固定的。
int 数组在内存中的结构
7.2 分析AtomicIntegerArray核心代码
private static final int base = unsafe.arrayBaseOffset(int[].class);
private static final int shift;
private final int[] array;
static {
int scale = unsafe.arrayIndexScale(int[].class);//拿到每个元素占用的字节
if ((scale & (scale - 1)) != 0)//判断是否为2的幕
throw new Error("data type scale not a power of two");
shift = 31 - Integer.numberOfLeadingZeros(scale);//相当于Integer.numberOfTrailingZeros。
}
private long checkedByteOffset(int i) {
if (i < 0 || i >= array.length)
throw new IndexOutOfBoundsException("index " + i);
return byteOffset(i);
}
private static long byteOffset(int i) {
return ((long) i << shift) + base;//i<<shift是指 i前面占用的offset
}