Atomic原理+问题
问题:
-
什么是Atomic? Atomic包下的原子类,是不可中断的一系列操作,保证了多线程下的安全性。 原子类是使用volatile和cas操作来保证原子性的,并不是锁 -
原子类有哪些? 1.基本类型的原子类(3)
AtomicInteger、AtomicLong、AtomicBoolean
2.数组类型原子类(3)
AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray<E>
3.引用类型(1)
AtomicReference
4.升级普通变量原子类(3)---不能升级被private、static修饰的普通变量
AtomicIntegerFiledUpdater<T>、AtomicLongFiledUpdater、AtomicReferenceFiledUpdater
5.Adder累加器(2)
LongAdder、DoubleAdder
6.Accumulator(2)
LongAccumulator、DoubleAccumulator
AtomicStampedReference<V> AtomicMarkableReference<V> 解决ABA问题
-
什么是CAS方法? 比较并交换
cas(56,57) 预期内存值是56 当前内存值为56 要修改为57 预期值与内存值比较,相同则修改为修改值 不同返回预期值56
CAS缺点 ABA问题 自旋时间过长
原子类现在直接掉本地方法去了
要想看自旋也可以看看这个方法
public final int getAndUpdate(IntUnaryOperator updateFunction) {
int prev, next;
do {
prev = get();
next = updateFunction.applyAsInt(prev);
} while (!compareAndSet(prev, next));
return prev;
}
ABA问题: cas只是比较预期值和内存值是否相同,可能A线程在比较,但是期间b线程将a值改为了b,c线程将b值改成了a 对于a线程来讲值并未被修改。
AtomicInteger为什么是原子类?
查看源代码可以看到:
private volatile int value;
AtomicInteger类的值被volatile修饰,这个volatile可以保证value值一被修改则对使用线程可见,这就保证了所有线程读的时候的可见性
比较常用的是它的自增自减
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
private static final Unsafe unsafe = Unsafe.getUnsafe();
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;
}
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
第一类是原子更新基本类型类,就三种基本方法都一样。
但是这里只提供了三种基本类型,要知道Java基本类型有8种,那么其它的怎么办?
1.Unsafe类提供的只有三个CAS方法
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);
也并无boolean型的啊
2.查看AtomicBoolean的源代码发现先把Boolean转换为整型然后使用compareAndSwapInt进行CAS的,
所以其它类型我们也可以这样操作
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);
}
原子更新数组
只看了AtomicIntegerArray,其它的和这个方法差不多
主要使用的方法就是原子更新数组中int形数据
-
public final int addAndGet(int i, int delta) {
return getAndAdd(i, delta) + delta;
}
public final int getAndAdd(int i, int delta) {
return unsafe.getAndAddInt(array, checkedByteOffset(i), delta);
}
-
public AtomicIntegerArray(int[] array) {
this.array = array.clone();
}
原子更新引用类型
? 原子的更新引用对象
? 常用方法
public final boolean compareAndSet(V expect, V update) {
return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}
AtomicMarkableReference,原子更新带有标记位的引用类型----CAS中比较旧值的同时比较标记位,如果旧值比较通过,标记位比较不通过还是不会去set 查看源码,怎么做的呢?
private volatile Pair<V> pair;
private static class Pair<T> {
final T reference;
final boolean mark;
private Pair(T reference, boolean mark) {
this.reference = reference;
this.mark = mark;
}
static <T> Pair<T> of(T reference, boolean mark) {
return new Pair<T>(reference, mark);
}
}
只有这一个构造方法
public AtomicMarkableReference(V initialRef, boolean initialMark) {
pair = Pair.of(initialRef, initialMark);
}
CAS方法
public boolean compareAndSet(V expectedReference,
V newReference,
boolean expectedMark,
boolean newMark) {
Pair<V> current = pair;
return
expectedReference == current.reference &&
expectedMark == current.mark &&
((newReference == current.reference &&
newMark == current.mark) ||
casPair(current, Pair.of(newReference, newMark)));
}
casPair方法
private static final long pairOffset =
objectFieldOffset(UNSAFE, "pair", AtomicMarkableReference.class);
private boolean casPair(Pair<V> cmp, Pair<V> val) {
return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
}
原子更新字段类/升级普通变量原子类
原子的更新某个类里的某个字段
书上是/前的描述,视频课里面是后面描述,感觉后面更清晰
我买的 2020 3月那版书就这节有明显的失误,Java并发编程的艺术
注意点:该普通变量不可被private修饰,不能是static 不看了和之前的差不多
重要的是一个
AtomicStampedReference,这个也是解决ABA问题的,只不过是以版本号的形式,就是用一个int值,作为版本号
和AtomicMarkableReference 相似,就是一个以boolean判断,一个int值
Adder 累加器 空间换时间 效率高
-
为什么要有这个? 因为AtomicLong累加效率差,是volatile修饰所以每次修改值都会通知所有线程,而线程每次读则都会去共享内存重新获取值 -
LongAdder则是通过空间换取时间 LongAdder每个线程内都会有自己的计数器,独立的就不需要去和其它线程同步 引入了分段累加的概念 1. 当竞争激烈的时候,会使用一个Cell[]数组,每个线程持有一个cell[i],先加到这里面
2. 竞争不激烈的时候,使用一个base变量,和AtomicLong一致 transient volatile long base;
-
分析源码 返回总和的源码,不能十分精确,可能遍历到中间前面的cell又有了变化 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;
}
Accumulator
更通用的Adder
提供了函数式接口,可以使用lambda
|