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();// Unsafe封装了原子操作硬件的方法
//java并不能直接操作硬件
//Unsafe类中的方法,可见调用了cas方法
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);//通过当前对象var1和对象在内存的的偏移量 确定当前value值
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));//如果比较旧value相等,那么就让旧value+1并替换旧vlaue
return var5;//返回的是旧value
}
//native说明此方法是个本地方法,就是调用本地的c++方法去cas修改值
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形数据
-
//delta+数组(i) 原子更新 返回更新后的值 public final int addAndGet(int i, int delta) { return getAndAdd(i, delta) + delta; } //和原子更新AtomicInteger一样的方法 public final int getAndAdd(int i, int delta) { return unsafe.getAndAddInt(array, checkedByteOffset(i), delta); }
-
//将数组传入的构造参数,会拷贝一份array,所以对数组的更改不会影响到原数组 public AtomicIntegerArray(int[] array) { // Visibility guaranteed by final field guarantees this.array = array.clone(); }
原子更新引用类型
原子的更新引用对象
常用方法
//先比较是期望的expect值,是则更新为update
public final boolean compareAndSet(V expect, V update) {
return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}
AtomicMarkableReference,原子更新带有标记位的引用类型----CAS中比较旧值的同时比较标记位,如果旧值比较通过,标记位比较不通过还是不会去set
查看源码,怎么做的呢?
//首先看到一个被volatile变量修饰的内部类,这个内部类封装了当前呆更新对象和标记位,修改这些变量可以对所以线程可见(volatile特性)
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方法
//expectedReference == current.reference cas比较
//expectedMark == current.mark 比较标记位
//如果新引用和当前引用==,且新标记位和旧标记位一致,那么就不执行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) || //这里如果前面()里面的都为true,那么就不会执行后面的cas操作,||特性
casPair(current, Pair.of(newReference, newMark)));
}
casPair方法
private static final long pairOffset =
objectFieldOffset(UNSAFE, "pair", AtomicMarkableReference.class);//通过对象的pair字段获取在内存的偏移量
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