AtomicInteger详解

2020-08-26 17:15:38 浏览数 (1)

一、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问题。

0 人点赞