1.基本
AtomicInteger是對Integer類型的一個包裝,提供原子性的通路和更新操作。其原子性的操作是基于CAS實作的。
CAS的過程是這樣,執行運算時,使用目前資料值作為判斷條件,利用CAS指令試圖進行更新。更新之前擷取記憶體中的最新值,與傳來的目前值作比較。如果數值沒有變,則說明沒有其他線程進行并發修改,更新操作成功。則否則要麼進行重試,要麼傳回結果。
2.AtomaticInteger的應用場景
AtomaticInteger最典型的應用場景是計數。比如我們要統計并發插入10萬條資料的耗時,我們需要對插入的資料計數,普通的int變量在多線程環境下的++操作,是線程不安全的,前一個操作可能會被後一個操作所覆寫,是以統計的技術往往小于準确值。這時候就可以使用AtomaticInteger。
使用非常簡單:
private AtomicInteger counter = new AtomicInteger(0);//初始計數為0
// doSomething,執行操作之後,計數即可
int count = counter.incrementAndGet();
3.源碼分析
基本屬性
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;
public 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語言實作的。這裡就不做深究了。
4.CAS操作的副作用
常用的失敗重試機制隐含着一個假設,就是競争情況是短暫的。在多數場景中,重試發生1到2次就成功了,但總有意外情況。是以有需要的時候,考慮自旋的次數,超過多少次之後就不再重試,避免過度消耗CPU.
還有一個就是著名的ABA問題。CAS是在更新時比較前值,如果前值恰好和最新值相同(不是邏輯上的相同),例如期間發生了A->B->A的更新,可能導緻不合理的操作。對于這種情況ava 提供了 AtomicStampedReference類,通過為引用建立版本号的方式,保證CAS的正确性。
5.AtomicInteger計數怎麼保證重置後的數值準确性
假設我們有這樣一個需求,統計每次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;
}
}