前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >AtomicInteger详解

AtomicInteger详解

作者头像
虞大大
发布2020-08-26 17:15:38
9670
发布2020-08-26 17:15:38
举报
文章被收录于专栏:码云大作战

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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-06-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码云大作战 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档