一、AtomicInteger
因为在阻塞队列中LinkedBlockingQueue中对容量的维护使用了Atomic类,所以需要对该类学习下,如何使用AtomicInteger来保证线程操作的原子性。
实际上源码中可以看出AtomicInteger类中的操作的思想基本上是基于CAS volatile。
二、AtomicInteger源码
· 结构
public class AtomicInteger extends Number
implements java.io.Serializable {
AtomicInteger继承了Number类,来实现数字类型的转行操作。
· 变量
代码语言:javascript复制//使用unsafe的CAS来更新
private static final Unsafe unsafe = Unsafe.getUnsafe();
//偏移量
private static final long valueOffset;
static {
try {
//初始化value的起始地址的偏移量
valueOffset = unsafe.objectFieldOffset
(java.util.concurrent.atomic.AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) {
throw new Error(ex);
}
}
//值 - 使用volatile修饰
private volatile int value;
变量中的值使用了volatile来修饰,并且声明了Unsafe,使用Unsafe来初始化偏移量。
Unsale类是JDK内部用的工具类,因此可以使用该类直接操作内存空间。所以他也被翻译称为不安全的类。
· 构造函数
代码语言:javascript复制//指定初始化的值
public AtomicInteger(int initialValue) {
value = initialValue;
}
//未指定
public AtomicInteger() {
}
· 直接方法
代码语言:javascript复制//获取值 返回value属性
public final int get() {
return value;
}
//直接设置新值
public final void set(int newValue) {
value = newValue;
}
· 原子方法
代码语言:javascript复制//直接操作内存 设置新值
public final void lazySet(int newValue) {
unsafe.putOrderedInt(this, valueOffset, newValue);
}
//直接操作内存 设置新值并且返回旧值
public final int getAndSet(int newValue) {
return unsafe.getAndSetInt(this, valueOffset, newValue);
}
· CAS方法
CAS是compare and swap的缩写,即在操作时会有三个值,分表是V(内存中的值),A(旧的原值),B(要修改的新值),只有内存中的值等于旧的原值即V=A时,B才能对内存中的值进行更新。CAS的采用了乐观锁的思想,比起传统的悲观锁(操作时将资源锁住,操作完成后释放锁)的性能,乐观锁也有了很大的提高。
不过CAS在应用中也有明显的缺点。
(1)CAS操作失败后,会继续尝试更新变量,如果一直更新不成功,会一直自旋。在高并发的情况下,CPU压力会因此增加。
(2)CAS只能保证2个变量旧值和新值的原子性操作,如果值超过2个的话,就不能使用CAS。
(3)CAS最大的问题 - 无法解决ABA问题(请看下一篇)。
代码语言:javascript复制//CAS修改值 expect为预期值即内存中的原值-A update为要修改的新值-B
//A等于内存值 才可以更新内存值为B,否则CAS自旋,重复更新操作
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);
}
代码语言:javascript复制//获取原值,并使用CAS将旧值 1
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
//获取原值,并使用CAS将旧值-1
public final int getAndDecrement() {
return unsafe.getAndAddInt(this, valueOffset, -1);
}
代码语言:javascript复制//获取原值,并使用CAS将旧值 指定值
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
代码语言:javascript复制//使用CAS将值 1 返回原结果,原结果最后再 1 为新值
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) 1;
}
//使用CAS将值-1 返回原结果,原结果最后再-1 为新值
public final int decrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
}
代码语言:javascript复制//使用CAS将值 指定值 返回原结果,原结果最后再 指定值 为新值
public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) delta;
}
代码语言:javascript复制//unsafe中的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));
//这一部进行CAS比较, //var2表示期望值B,var5表示内存值V, //当A=V,会进行更新操作var5 var4 (要新增的值,加起来就是新值B,将B更新给C)
//返回原来的值
return var5;
}
三、AtomicInteger分析
从上面的源码中可以看到,我们内部使用了CAS原理,可以解决多线程情况下的原子性操作,比如保证在多线程高并发情况下的i 的操作,虽然输出的结果并不是串行的(多线程情况下肯定不是顺序执行),但是可以保证他们最终的结果值是可以达到预期值的。CAS的优缺点也已在上面总结了。
另外AtomicInteger、AtomicLong、AtomicBoolean等的思想也与AtomicInteger类似,因此不具体展开分析。
· 为什么CAS无法解决ABA问题?
这里还是要重点分析下ABA问题。举个例子。。
场景:银行汇钱并发行为,分析下我们的账户会有怎么样的改变。
(操作一)你的银行账户里有100元,你需要取50元。
(操作二)用户无感知情况下,重复提交了线程。
(操作三)他人给你转入了50元。
操作一:V-内存值为100,A-期望原值100,B-新值50。V=A,所以更新成功,此时的V被扣除了50变成了50,此时的账户为50元(操作一成功)。
操作二:由于操作二和操作一存在并发行为,因此A-取出的内存原值仍为100,但是此时的V已经被更新成了50,B-新值50。V!=A,所以更新失败。CPU挂起,循环判断V是否等于A(操作二挂起)。
操作三:V-内存值仍为50,A取出的内存原值-50,B-新值50。V=A,所以更新成功,此时的V增加了50变成了100,此时的账户为100元(操作三成功)。
操作二:循环判断中,由于操作三的操作成功,因此A取出的内存原值变为了100,操作二中计算得出的V为100,因此V=A,所以更新成功,此时的V被扣除了50元变成了50元,此时的账户为50元。(操作二成功)。
这种场景下,实际上用户认为自己的账户实际上应该为100元,但是由于ABA问题,导致在用户无感知,系统多次重复提交的情况下而多扣了50元。因此CAS不能很好的解决ABA问题,下面一篇会将如何解决ABA问题。