AtomicInteger是对Integer类型的一个包装,提供原子性的访问和更新操作。其原子性的操作是基于CAS实现的。CAS的过程是这样,执行运算时,使用当前数据值作为判断条件,利用CAS指令试图进行更新。更新之前获取内存中的最新值,与传来的当前值作比较。如果数值没有变,则说明没有其他线程进行并发修改,更新操作成功。则否则要么进行重试,要么返回结果。
应用场景
AtomaticInteger最典型的应用场景是计数。比如我们要统计并发插入10万条数据的耗时,我们需要对插入的数据计数,普通的int变量在多线程环境下的++操作,是线程不安全的,前一个操作可能会被后一个操作所覆盖,所以统计的技术往往小于准确值。这时候就可以使用AtomaticInteger。使用非常简单:
private AtomicInteger counter = new AtomicInteger(0);//初始计数为0 // doSomething,执行操作之后,计数即可 int count = counter.incrementAndGet();
源码分析
基本属性
private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } private volatile int value; ublic final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); } public final int getAndDecrement() { return unsafe.getAndAddInt(this, valueOffset, -1); }
可以看到,AtomicInteger的操作基本都依赖于unsafe提供的底层支持。Unsafe 会利用 value 字段的内存地址偏移,完成操作。进入Unsafe源码:
public final int getAndSetInt(Object var1, long var2, int var3) { int var5; do { var5 = this.getIntVolatile(var1, var2);//当前线程这个时刻,根据AtomicInteger对象和value的内存地址偏移,获取到value值 } while(!this.compareAndSwapInt(var1, var2, var5,var3)); //while条件compareAndSwapInt是CAS操作,,如果当前值和执行操作前的最新值一致,则将value加1,否则操作失败,返回false,继续获取最新值,直到更新操作成功。 return var5; } public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
可以看到Unsafe的compareAndSwapInt是用native,native 关键字告诉编译器(其实是JVM)调用的是该方法在外部定义,实际是使用C语言实现的。这里就不做深究了。
CAS操作的副作用
常用的失败重试机制隐含着一个假设,就是竞争情况是短暂的。在多数场景中,重试发生1到2次就成功了,但总有意外情况。所以有需要的时候,考虑自旋的次数,超过多少次之后就不再重试,避免过度消耗CPU.还有一个就是著名的ABA问题。CAS是在更新时比较前值,如果前值恰好和最新值相同(不是逻辑上的相同),例如期间发生了A->B->A的更新,可能导致不合理的操作。对于这种情况ava 提供了 AtomicStampedReference类,通过为引用建立版本号的方式,保证CAS的正确性。
如何保证重置后的数值准确性
假设有这样一个需求,统计每次10w条插入数据的耗时,计数到10w之后,就需要重置为0,先看下代码:
private AtomicInteger counter = new AtomicInteger(0);//初始计数为0 private long lastTime = 0; public void insert(){ //insert if(counter.incrementAndGet() == 100000) { counter.set(0); long currentTime = System.currentTimeMillis(); log.info("\n\n=============== insert 10w data,time="+ currentTime+",used"+(currentTime-lastTime)+"'s ================\n\n"); lastTime = currentTime; } }
counter.incrementAndGet()的值大于10w时,我们使用set方法,将value值重新置为0。多线程环境下,可能出现多个线程同时执行counter.incrementAndGet()这句代码(还没有执行它的返回值==10w的判断),第一个线程执行后是99999,不满足条件,后面几个线程计数增加到超过10w,而这时执行计数结果是10w那个线程满足条件(==10w),重置为0,那么就丢掉了超出10w的几个计数。计数就不准确了。当然条件是“>=”的时候,计数仍然不准确,而且会执行多次满足条件后的语句,打印多次日志,这显然不是我们想要的结果。有什么办法可以实现准确计数呢?AtomicInteger提供了一个updateAndGet方法,参数是实现IntUnaryOperator的类。看下它的实现:
public final int updateAndGet(IntUnaryOperator updateFunction) { int prev, next; do { prev = get(); next = updateFunction.applyAsInt(prev); } while (!compareAndSet(prev, next)); return next; }
updateFunction.applyAsInt(prev)这个方法返回我们希望重置的值。这样就简单了,我们只需要将超出部分的值,从applyAsInt方法返回就行了。具体的实现代码:
private AtomicInteger counter = new AtomicInteger(0);//初始计数为0 private long lastTime = 0; public void insert(){ //insert if(counter.incrementAndGet() >= 100000) { counter.updateAndGet(new CounterVar()); long currentTime = System.currentTimeMillis(); log.info("\n\n=============== insert 10w data,time="+ currentTime+",used"+(currentTime-lastTime)+"'s ================\n\n"); lastTime = currentTime; } } public class CounterVar implements IntUnaryOperator{ @Override public int applyAsInt(int value) { if(value >= 100000) { return value-100000; } return value; } }