2.1 AtomicInteger和AtomicLong
AtomicInteger和AtomicLong可以实现自增或者自减的原子操作。
AtomicInteger jdk 1.8源码如下:
AtomicInteger类中的方法
private static final long VALUE;
//内存偏移量
static {
try {
VALUE = U.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (ReflectiveOperationException e) {
throw new Error(e);
}
}
public final int getAndIncrement() {
return U.getAndAddInt(this, VALUE, 1);
}
Unsafe类中的方法
//var5 旧值 var5+var4 新值
//var2 内存偏移量,后面cas的操作是对内存偏移量的操作
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;
}
2.1.1 悲观锁与乐观锁
悲观锁:认为并发冲突的概率很大,所以读操作之前就上锁。synchronized(jdk1.6之前)和ReentrantLock都是悲观锁。
乐观锁:认为并发冲突概率比较小,所以读操作之前不上锁。等到写操作的时候,在判断数据在此期间是否被其他线程修改。如果被修改则读出来,重复该过程。如果没有被修改,就写回去。
CAS:判断数据是否被修改,同时写回新值,这两个操作合成了一个原子操作,也就是CAS(Compare And Set)。
AtomicInteger和AtomicLong是典型的乐观锁。
2.1.2 Unsafe的CAS详解
var5是旧值,var5+var4是新值,当var5等于当前的值时,说明在修改期间没有其他线程对此变量进行修改,所以可以写入,返回true。否则不能写入,返回false。
var2是内存偏移量,后面的cas操作都是对var2的操作。
2.1.3 自旋与阻塞
当线程拿不到锁的时候有两个等待策略。
阻塞:放弃cpu,进入阻塞状态,等待后续被唤醒。
自旋:不放弃cpu,空转,不断重试。
2.2 AtomicBoolean和AtomicReference
2.2.1 AtomicBoolean解决下面问题:
if(!flag){
flag=true;
}
上述不是原子操作,所以需要AtomicBoolean完成,操作如下:
AtomicBoolean类中
public final boolean compareAndSet(boolean expect, boolean update) {
return U.compareAndSwapInt(this, VALUE,
(expect ? 1 : 0),
(update ? 1 : 0));
}
2.2.2 AtomicReference:对象引用变量
2.2.3 如何支持boolean和double类型
boolean转换成int类型(在AtomicBoolean类中),double转换成long类型(注意double没有Atomic类,是Double自身提供的方法)
2.3 ABA问题解决方案
2.3.1 ABA问题与解决方法
①.问题:前面说的cas操作都是基于“值”来比较的,但是一个线程把变量的值从A改成B,再改成A,其他线程却因为值的没有改变来判定没有被修改过。
②.解决方案:不仅要比较“值”,还要比较“版本号”。
AtomicStampedReference就是先比较值在比较版本号。
2.3.2 为什么没有AtomicStampedInteger和AtomicStampedLong
因为是同时比较数据的“值”和“版本号”,然后封装成一个对象,是Pair内部类,然后通过对象引用的cas来实现,因为Integer和Long无法同时比较两个变量值,所以不存在AtomicStampedInteger和AtomicStampedLong,如下是AtomicStampedReference源码
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)));
}
2.3.3 AtomicMarkableReference:版本号用boolean类型的,相对AtomicStampedReference存在风险
2.4 AtomicIntegerFieldUpdater、AtomicLongFieldUpdater和AtomicReferenceFieldUpdater
解决在类的成员变量不是Atomic类型情况下,并且不修改源码的情况下,用上面三种方式可以实现原子操作
原理:内部通过反射拿到这个类的成员变量,然后包装成AtomicXXXFieldUpdater类
限制条件:使用AtomicXXXFieldUpdater修改的成员变量必须是有volatile修饰的
2.5 AtomicIntegerArray、AtomicLongArray和AtomicReferenceArray
作用:数组元素的原子操作,注意不是整个数组的原子操作,而是数组中一个元素的原子操作
用法:相比AtomicXXX多一个数组下标参数
2.6 Striped64与LongAdder
Striped64子类有:LongAdder、LongAccumulator、DoubleAdder、DoubleAccumulator
2.6.1 LongAdder原理:将变量拆成多份,分别是一个base变量和多个Cell,如果并发量低对base进行操作,如果并发量高对Cell进行操作,相对于AtomicXXX更加高效,提高性能,如下图:
源码如下:
abstract class Striped64 extends Number {
transient volatile Striped64.Cell[] cells;
transient volatile long base;
...
//此处Contended是缓存行填充,后面会介绍
@Contended
static final class Cell {
volatile long value;
...
}
}
2.6.2 最终一致性
如上方式是完成了最终一致性操作,不是强一致性。因为一边有现成在执行求和操作,一边有线程在修改数组里的数据。
2.6.3 伪共享与缓存行填充
jdk 8重要的注解@Contended涉及一个很重要的优化原理:伪共享与缓存行填充
如下变量X,Y,Z缓存在cpu里面同一行,如果X变量被修改导致失效,则Y,Z都失效,这就是伪共享。
加了@Contended注释就是具有缓存行填充,可以防止Y,Z失效。JDK 1.7也有,不过不是注释的形式。
2.6.4 LongAdder 核心实现
①.LongAdder最核心的是累加函数add(long x),自增自减都是最终调用这个函数。源码如下
public void add(long var1) {
Cell[] var3;
long var4;
if ((var3 = this.cells) != null || !this.casBase(var4 = this.base, var4 + var1)) {//第一次尝试加到base上面
boolean var10 = true;
long var6;
int var8;
Cell var9;
if (var3 == null || (var8 = var3.length - 1) < 0 || (var9 = var3[getProbe() & var8]) == null || !(var10 = var9.cas(var6 = var9.value, var6 + var1))) {//第二次尝试加到cell上面
this.longAccumulate(var1, (LongBinaryOperator)null, var10);
}
}
}
如上所示,第一次尝试加到base上,如果不成功则第二次尝试加到cell上,如果不成功则调用longAccumulate方法。
longAccumulate的代码较长,里面主要做的事是:初始化数组,对数组进行扩容,每次扩容是原先的两倍,然后将新的数据加到扩容的数组中
2.6.5 LongAccumulator
原理和LongAdder类似,功能更强大
LongAdder只能累加操作,并且初始化值默认是0,LongAccumulator可以自定义一个二元操作符,并且可以传入一个初始值。
2.6.6 DoubleAdder与DoubleAccumulator
原理:转换成long进行操作的