今天的筆記來了解一下原子操做以及Java中如何實現原子操做。java
原子(atomic)本意是「不能被進一步分割的最小粒子」,而原子操做(atomic operation)意爲「不可被中斷的一個或一系列操做」。算法
處理器會保證基本內存操做的原子性。處理器保證從系統內存中讀取或者寫入一個字節是原子的,意思是當一個處理器讀取一個字節時,其餘處理器不能訪問這個字節的內存地址。最新的處理器能自動保證單處理器進行16/32/64位的操做是原子的,而且提供總線鎖定和緩存鎖定兩個機制來保證複雜內存操做的原子性。數組
若是有多個處理器同時對共享變量進行操做,那麼共享變量就會被多個處理器同時操做,這樣的話,讀改寫操做就不是原子的。
好比i=1,i++,兩個處理器同時進行操做,最後的結果,多是3,也多是3.
緣由多是多個處理器同時從各自的緩存中讀取變量i,分別進行加1操做,而後分別寫入系統內存。
處理器使用總線鎖來解決這個問題。當處理器發出LOCK#信號時,其餘處理器的請求會被阻塞主,該處理器能夠獨佔共享內存。緩存
鎖總線開銷仍是很大的,鎖住了CPU和內存之間的通訊。
由於頻繁使用的內存會緩存在處理器的L一、L二、L3高速緩存中,原子操做能夠在緩存內部完成,同時經過緩存一致性協議,當A處理器修改緩存中的i時,其餘處理器不能同時緩存i,即會使得其餘處理器中對於共享變量的緩存失效。
這段還不是特別明白,感受得從新翻一下操做系統,有知道的網友能夠留言補充一下。this
Java可使用鎖,實現一段代碼的原子操做。但這樣開銷比較大,會引發頻繁的上下文切換。
另一種方式就是使用CAS操做(比較交換)。
CAS算法的過程是比較簡單的。它會包含三個參數(V,E,N))。V表示要更新的變量,E表示預期值,N值。當且僅當V等於E值時,纔會將V的值設爲N,若是V值和E值不一樣,說明已經有其餘線程作了更新,則當前線程什麼都不作。
當多個線程同時使用CAS對變量進行操做時,只有一個會勝出併成功更新,其他會失敗。失敗的線程不會被掛起。
Java中對於基本類型的包裝類都有對應的原子操做實現,好比
AtomicBoolean
AtomicInteger
等
若是拿AtomicInteger爲例子,其中的incrementAndGet的實現以下所示,是直接調用了Unsafe類的方法:atom
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)); return var5; }
其中var1是傳入對象的引用,var2是字段到對象頭部的偏移量,方便快速定位,var5是當前值,var5+var4就是指望值,var4傳入的是1。
若是是引用類型的話,可使用AtomicReference。
CAS操做雖好,但它會遇到ABA問題,即一個變量先是A,後來變成了B,在比較時又變回了A,但CAS操做沒法感知到這種狀況,若是說咱們是否能夠修改當前值,不只取決於當前值,還取決於它的變化,那麼原有的CAS操做就無能爲力了,由於它感知不到。
貼心的JDK爲咱們提供了AtomicStampedReference,它在對象內部維護了時間戳,當更新數據時,不只要更新數據,還要更新時間戳。當AtomicStampedReference設置對象值時,對象值以及時間戳都必須知足指望值,寫入纔會成功。
若是是數組類型的話,JDK提供了AtomicIntegerArray等數組類型的原子類。spa