java併發和高併發之 線程安全性——原子性-atomic-1

一、線程安全性

1、基本概念:

》定義:當多個線程訪問某個類時,不管運行時環境採用何種調度方式或者這些進行將如何交替執行,並且在主調代碼中不需要任何額外的同步或協同,這個類都能表現出正確的行爲,那麼就稱這個類是線程安全的。

2、線程安全性的幾個表現點:

》原子性:提供了互斥訪問,同一時刻只能有一個線程來對它進行操作;

》可見性:一個線程對主內存的操作可以及時地被其他線程觀察到;

》有序性:一個線程觀察其他線程中的指令執行順序,由於指令重排序的存在,該觀察結果一般雜亂無序;

3、原子性 ——jdbc中的 Atomic包:

》AtomicXXX:CAS 、Unsafe.compareAndSwapInt

》AtomicLong、LongAdd

》AtomicReference、AtomicReferenceFieldUpdater

》AtomicStampReference: CAS的ABA問題

4、Atomic包實例分析

1)AtomicXXX:CAS 、Unsafe.compareAndSwapInt

如下爲對原來的累加計數實例進行原子性優化後的代碼:

優化前的代碼:

優化後的代碼:

 

注意上方這裏的Add方法的差異,以及add裏面用到了count,注意其上方count 定義的差異。

源碼分析:

incrementAndGet()方法中,用到了unsafe 類的getAddInt 方法,主要的是該方法的實現是關鍵。該實現的原理爲,在其中使用了一個do  while 循環,而while的循環條件中,用到了this.compareAndSwapInt(var1,var2,var5,var5 +var4); 

 getAndInt () 方法中,第一個參數var1 是個對象,表示調用者,如上即爲count ,第二個值是當前的值,比如要執行2+1 ,則第二個值就是2,第三個值就是1。而compareAndSwapInt 是個native修飾的java底層的方法。compareAndSwapInt 方法中的幾個參數,含義爲,調用對象 比如此處count,當前值var2 和從底層獲取的值 var5如果相等,則返回var5+ var4

CompareAndSwapXXX 也就是CAS的名稱由來。

擴展:Atomic包中還有個AtomicBoolean類,該類常用於如下情形:希望某個方法只執行一次甚至當前只能有一個線程執行這段代碼,執行前有個標誌變量,該變量在某個方法執行前爲false,執行後變爲true,此時使用其中compareAndSet(boolean expect,boolean update)方法非常合適。

2)

將上方的AtomicInteger 改爲AtomicLong 類,其他不用動,進行測試

 

 

 如上爲AtomicLong 類的使用,如上所示,只是將原來的AtomicInteger換成了AtomicLong ,其他都沒變。

如下爲使用jdk8中新的LongAdder類的使用,此處除了修改count的類型定義,

也根據LongAdder 的對應方法.

擴展:對於普通的Long和Double類型變量,jvm允許將64位的讀操作或寫操作,拆成2個32位的讀操作或者寫操作進行計算。LongAdder的實現原理:將熱點數據分離,比如將AtomicLong的內部核心數據value分離成一個數組,每個線程好運時,通過hash等算法映射到其中一個數字進行計數,而最終的計數結果,則爲這個數組的求和累加。其中熱點數據value會被分離成多個單元的cell,每個cell獨自維護內部的值,當前對象的實際值,由所有的cell累計合成。這樣熱點就進行了有效的分離,並提高了並行度。LongAdder是在AtocmicXXX的基礎上,將單點的更新壓力分散到各個節點上。在低併發時,通過對base的直接更新,可以很好地保證和atomic的性能基本一致。在高併發時,通過分散提高了性能。LongAdder也有自己的缺陷,缺點是在某些時,如果有併發更新,可能會導致統計的數據由誤差。所以在實際使用中,如果高併發時,可以使用LongAdder。在低併發時,使用Atomic更簡單直接高效準確。但對於序列號生成等特別準確時,適合AtomicLong,而不是LongAdder

 

 

 3)AtomicReference、AtomicReferenceFieldUpdater

先看示例:

 

 執行結果爲何是4呢?原因如下:

下面展示第二個類的實例:

這個類,核心是向原子性去更新某一個類的實例的某個被volatile修飾的但一定不能被static修飾的一個字段,比如上方的實例中,更新example5,的一個被volatile修飾但不被static修飾的字段count。本質就是對原子性的變量進行修改。

如上的實例含義爲:第一個if時,判斷count值是否爲100,如果是,則更新爲120.因爲滿足條件,所以執行,結果count變爲120.當走到第二個if時,做同樣的判斷,但此時count爲120,不符合條件,所以執行else語句。

4)AtomicStampReference: 核心是要解決CAS的ABA問題。

ABA 問題指的是,在CAS操作的時候,其他線程將A的值改爲了B,但是又改回了A,本線程使用期望值A與當前變量進行比較時,發現A變量沒有變,於是CAS就將A進行了交換操作,實際上該值已經被其他線程改變過,這與設計思想不符合。因此ABA問題的解決思路就是每次更新時,將變量的版本號加1,那麼之前的A改爲B 再改爲A,就有了一個版本號的變化。因此,某個變量一旦被某個線程修改過,就會有版本記錄,從而解決ABA問題。實質用的是可見性原理。

該類AtomicStampReference中的關鍵方法爲compareAndSet()方法。此處不再舉例。

再介紹一個類:AtomicLongArray  也是類似的。

此處再用示例介紹下AtomicBoolean類。。

 

執行結果:

 如上方法只會執行一次,因爲test()方法中的判斷,當執行過一次後,isHappened.compareAndSet(false,true)會將isHappened變量置爲true,從而不符合執行後面循環的條件,而只是執行一次,絕對不會重複。

如果遇到期待某段邏輯只是執行一次,可仿照上例。

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章