Java JUC之Atomic系列12大類實例講解和原理分解

在java6之後咱們不但接觸到了Lock相關的鎖,也接觸到了不少更加樂觀的原子修改操做,也就是在修改時咱們只須要保證它的那個瞬間是安全的便可,通過相應的包裝後能夠再處理對象的併發修改,以及併發中的ABA問題,本文講述Atomic系列的類的實現以及使用方法,其中包含:java

基本類:AtomicInteger、AtomicLong、AtomicBoolean;數據庫

引用類型:AtomicReference、AtomicReference的ABA實例、AtomicStampedRerence、AtomicMarkableReference;數組

數組類型:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray安全

屬性原子修改器(Updater):AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater併發

 

看到這麼多類,你是否以爲很困惑,其實沒什麼,由於你只須要看懂一個,其他的方法和使用都是大同小異的,相關的類會介紹他們之間的區別在哪裏,在使用中須要注意的地方便可。app

 

在使用Atomic系列前,咱們須要先知道一個東西就是Unsafe類,全名爲:sun.misc.Unsafe,這個類包含了大量的對C代碼的操做,包括不少直接內存分配以及原子操做的調用,而它之因此標記爲非安全的,是告訴你這個裏面大量的方法調用都會存在安全隱患,須要當心使用,不然會致使嚴重的後果,例如在經過unsafe分配內存的時候,若是本身指定某些區域可能會致使一些相似C++同樣的指針越界到其餘進程的問題,不過它的具體使用並非本文的重點,本文重點是Atomic系列的內容大多會基於unsafe類中的如下幾個本地方法來操做:dom

 

對象的引用進行對比後交換,交換成功返回true,交換失敗返回false,這個交換過程徹底是原子的,在CPU上計算完結果後,都會對比內存的結果是否仍是原先的值,若不是,則認爲不能替換,由於變量是volatile類型因此最終寫入的數據會被其餘線程看到,因此一個線程修改爲功後,其餘線程就發現本身修改失敗了。測試

參數1:對象所在的類自己的對象(通常這裏是對一個對象的屬性作修改,纔會出現併發,因此該對象所存在的類也是有一個對象的)this

參數2:這個屬性在這個對象裏面的相對便宜量位置,其實對比時是對比內存單元,因此須要屬性的起始位置,而引用就是修改引用地址(根據OS、VM位數和參數配置決定寬度通常是4-8個字節),int就是修改相關的4個字節,而long就是修改相關的8個字節。atom

獲取偏移量也是經過unsafe的一個方法:objectFieldOffset(Fieldfield)來獲取屬性在對象中的偏移量;靜態變量須要經過:staticFieldOffset(Field field)獲取,調用的總方法是:fieldOffset(Fieldfield)

 

參數3:修改的引用的原始值,用於對比原來的引用和要修改的目標是否一致。

參數4:修改的目標值,要將數據修改爲什麼。

[java] view plain copy

 在CODE上查看代碼片派生到個人代碼片

  1. public final native boolean compareAndSwapObject(Object paramObject1, long paramLong, Object paramObject2, Object paramObject3);  
  2.   
  3. public final native boolean compareAndSwapInt(Object paramObject, long paramLong, int paramInt1, int paramInt2);  

 

#對long的操做,要看VM是否支持對Long的CAS,由於有可能VM自己不支持,若不支持,此時運算會變成Lock方式,不過如今VM都基本是支持的而已。

[java] view plain copy

 在CODE上查看代碼片派生到個人代碼片

  1. public final native boolean compareAndSwapLong(Object paramObject, long paramLong1, long paramLong2, long paramLong3);  

 

咱們不推薦直接使用unsafe來操做原子變量,而是經過java封裝好的一些類來操做原子變量。

 

實例代碼1:AtomicIntegerTest.java

 

[java] view plain copy

 在CODE上查看代碼片派生到個人代碼片

  1. import java.util.concurrent.atomic.AtomicInteger;  
  2. public class AtomicIntegerTest {  
  3.   
  4.     /** 
  5.      * 常見的方法列表 
  6.      * @see AtomicInteger#get()             直接返回值 
  7.      * @see AtomicInteger#getAndAdd(int)    增長指定的數據,返回變化前的數據 
  8.      * @see AtomicInteger#getAndDecrement() 減小1,返回減小前的數據 
  9.      * @see AtomicInteger#getAndIncrement() 增長1,返回增長前的數據 
  10.      * @see AtomicInteger#getAndSet(int)    設置指定的數據,返回設置前的數據 
  11.      *  
  12.      * @see AtomicInteger#addAndGet(int)    增長指定的數據後返回增長後的數據 
  13.      * @see AtomicInteger#decrementAndGet() 減小1,返回減小後的值 
  14.      * @see AtomicInteger#incrementAndGet() 增長1,返回增長後的值 
  15.      * @see AtomicInteger#lazySet(int)      僅僅當get時纔會set 
  16.      *  
  17.      * @see AtomicInteger#compareAndSet(int, int) 嘗試新增後對比,若增長成功則返回true不然返回false 
  18.      */  
  19.     public final static AtomicInteger TEST_INTEGER = new AtomicInteger(1);  
  20.       
  21.     public static void main(String []args) throws InterruptedException {  
  22.         final Thread []threads = new Thread[10];  
  23.          for(int i = 0 ; i < 10 ; i++) {  
  24.              final int num = i;  
  25.              threads[i] = new Thread() {  
  26.                  public void run() {  
  27.                      try {  
  28.                         Thread.sleep(1000);  
  29.                     } catch (InterruptedException e) {  
  30.                         e.printStackTrace();  
  31.                     }  
  32.                     int now = TEST_INTEGER.incrementAndGet();  
  33.                     System.out.println("我是線程:" + num + ",我獲得值了,增長後的值爲:" + now);  
  34.                  }  
  35.              };  
  36.              threads[i].start();  
  37.          }  
  38.          for(Thread t : threads) {  
  39.              t.join();  
  40.          }  
  41.          System.out.println("最終運行結果:" + TEST_INTEGER.get());  
  42.     }  
  43. }<strong>  
  44. </strong>  

 

代碼例子中模擬多個線程併發對AtomicInteger進行增長1的操做,若是這個數據是普通類型,那麼增長過程當中出現的問題就是兩個線程可能同時看到的數據都是同一個數據,增長完成後寫回的時候,也是同一個數據,可是兩個加法應當串行增長1,也就是加2的操做,甚至於更加特殊的狀況是一個線程加到3後,寫入,另外一個線程寫入了2,還越變越少,也就是不能獲得正確的結果,在併發下,咱們模擬計數器,要獲得精確的計數器值,就須要使用它,咱們但願獲得的結果是11,能夠拷貝代碼進去運行後看到結果的確是11,順然輸出的順序可能不同,也同時能夠證實線程的確是併發運行的(只是在輸出的時候,徵用System.out這個對象也不必定是誰先搶到),可是最終結果的確是11。

 

相信你對AtomicInteger的使用有一些瞭解了吧,要知道更多的方法使用,請參看這段代碼中定義變量位置的註釋,有關於AtomicInteger的相關方法的詳細註釋,能夠直接跟蹤進去看源碼,註釋中使用了簡單的描述說明了方法的用途。

 

而對於AtomicLong呢,其實和AtomicInteger差很少,惟一的區別就是它處理的數據是long類型的就是了;

對於AtomicBoolean呢,方法要少一些,常見的方法就兩個:

[java] view plain copy

 在CODE上查看代碼片派生到個人代碼片

  1. AtomicBoolean#compareAndSet(boolean, boolean)  第一個參數爲原始值,第二個參數爲要修改的新值,若修改爲功則返回true,不然返回false  
  2. AtomicBoolean#getAndSet(boolean)   嘗試設置新的boolean值,直到成功爲止,返回設置前的數據  

 

由於boolean值就兩個值,因此就是來回改,相對的不少增長減小的方法天然就沒有了,對於使用來說,咱們列舉一個boolean的併發修改,僅有一個線程能夠修改爲功的例子:

實例代碼2:AtomicBooleanTest.java

[java] view plain copy

 在CODE上查看代碼片派生到個人代碼片

  1. import java.util.concurrent.atomic.AtomicBoolean;  
  2.   
  3. public class AtomicBooleanTest {  
  4.   
  5.     /** 
  6.      * 主要方法: 
  7.      * @see AtomicBoolean#compareAndSet(boolean, boolean)  第一個參數爲原始值,第二個參數爲要修改的新值,若修改爲功則返回true,不然返回false 
  8.      * @see AtomicBoolean#getAndSet(boolean)   嘗試設置新的boolean值,直到成功爲止,返回設置前的數據 
  9.      */  
  10.     public final static AtomicBoolean TEST_BOOLEAN = new AtomicBoolean();  
  11.       
  12.     public static void main(String []args) {  
  13.         for(int i = 0 ; i < 10 ; i++) {  
  14.             new Thread() {  
  15.                 public void run() {  
  16.                     try {  
  17.                         Thread.sleep(1000);  
  18.                     } catch (InterruptedException e) {  
  19.                         e.printStackTrace();  
  20.                     }  
  21.                     if(TEST_BOOLEAN.compareAndSet(false, true)) {  
  22.                         System.out.println("我成功了!");  
  23.                     }  
  24.                 }  
  25.             }.start();  
  26.         }  
  27.     }  
  28. }  

 

這裏有10個線程,咱們讓他們幾乎同時去徵用boolean值的修改,修改爲功者輸出:我成功了!此時你運行完你會發現只會輸出一個「我成功了!」,說明徵用過程當中達到了鎖的效果。

 

 

那麼幾種基本類型就說完了,咱們來看看裏面的實現是否是如咱們開始說的Unsafe那樣,看幾段源碼便可,咱們看下AtomicInteger的一些源碼,例如開始用的:incrementAndGet方法,這個,它的源碼是:

[java] view plain copy

 在CODE上查看代碼片派生到個人代碼片

  1. public final int incrementAndGet() {  
  2.     for (;;) {  
  3.         int current = get();  
  4.         int next = current + 1;  
  5.         if (compareAndSet(current, next))  
  6.             return next;  
  7.     }  
  8. }  

能夠看到內部有一個死循環,只有不斷去作compareAndSet操做,直到成功爲止,也就是修改的根本在compareAndSet方法裏面,能夠去看下相關的修改方法均是這樣實現,那麼看下compareAndSet方法的body部分是:

[java] view plain copy

 在CODE上查看代碼片派生到個人代碼片

  1. public final boolean compareAndSet(int expect, int update) {  
  2.     return unsafe.compareAndSwapInt(this, valueOffset, expect, update);  
  3. }  

 

能夠看到這裏使用了unsafe的compareAndSwapInt的方法,很明顯this就是指AtomicInteger當前的這個對象(這個對象不用像上面說的它不能是static和final,它無所謂的),而valueOffset的定義是這樣的:

[java] view plain copy

 在CODE上查看代碼片派生到個人代碼片

  1. private static final long valueOffset;  
  2.   
  3.     static {  
  4.       try {  
  5.         valueOffset = unsafe.objectFieldOffset  
  6.             (AtomicInteger.class.getDeclaredField("value"));  
  7.       } catch (Exception ex) {   
  8.          throw new Error(ex); }  
  9. }  

能夠看出是經過咱們前面所述的objectFieldOffset方法來獲取的屬性偏移量,因此你本身若是定義相似的操做的時候,就要注意,這個屬性不能是靜態的,不然不能用這個方法來獲取。

 

後面兩個參數天然是對比值和須要修改的目標對象的地址。

其實Atomic系列你看到這裏,java層面你就知道差很少了,其他的就是特殊用法和包裝而已,剛纔咱們說了unsafe的3個方法無非是地址和值的區別在內存層面是沒有本質區別的,由於地址自己也是數字值。

 

爲了說明這個問題,咱們就先說Reference的使用:

咱們測試一個reference,和boolean測試方式同樣,也是測試多個線程只有一個線程能修改它。

實例代碼1:AtomicReferenceTest.java

[java] view plain copy

 在CODE上查看代碼片派生到個人代碼片

  1. import java.util.concurrent.atomic.AtomicReference;  
  2.   
  3. public class AtomicReferenceTest {  
  4.   
  5.     /** 
  6.      * 相關方法列表 
  7.      * @see AtomicReference#compareAndSet(Object, Object) 對比設置值,參數1:原始值,參數2:修改目標引用 
  8.      * @see AtomicReference#getAndSet(Object) 將引用的目標修改成設置的參數,直到修改爲功爲止,返回修改前的引用 
  9.      */  
  10.     public final static AtomicReference <String>ATOMIC_REFERENCE = new AtomicReference<String>("abc");  
  11.       
  12.     public static void main(String []args) {  
  13.         for(int i = 0 ; i < 100 ; i++) {  
  14.             final int num = i;  
  15.             new Thread() {  
  16.                 public void run() {  
  17.                     try {  
  18.                         Thread.sleep(Math.abs((int)(Math.random() * 100)));  
  19.                     } catch (InterruptedException e) {  
  20.                         e.printStackTrace();  
  21.                     }  
  22.                     if(ATOMIC_REFERENCE.compareAndSet("abc", new String("abc"))) {  
  23.                         System.out.println("我是線程:" + num + ",我得到了鎖進行了對象修改!");  
  24.                     }  
  25.                 }  
  26.             }.start();  
  27.         }  
  28.     }  
  29. }  

 

測試結果如咱們所料,的確只有一個線程,執行,跟着代碼:compareAndSet進去,發現源碼中的調用是:

[java] view plain copy

 在CODE上查看代碼片派生到個人代碼片

  1. public final boolean compareAndSet(V expect, V update) {  
  2.     return unsafe.compareAndSwapObject(this, valueOffset, expect, update);  
  3. }  

 

OK,的確和咱們上面所講一致,那麼此時咱們又遇到了引用修改的新問題,什麼問題呢?ABA問題,什麼是ABA問題呢,當某些流程在處理過程當中是順向的,也就是不容許重複處理的狀況下,在某些狀況下致使一個數據由A變成B,再中間可能通過0-N個環節後變成了A,此時A不容許再變成B了,由於此時的狀態已經發生了改變,例如:銀行資金裏面作一批帳目操做,要求資金在80-100元的人,增長20元錢,時間持續一天,也就是後臺程序會不斷掃描這些用戶的資金是不是在這個範圍,可是要求增長過的人就不能再增長了,若是增長20後,被人取出10元繼續在這個範圍,那麼就能夠無限套現出來,就是ABA問題了,相似的還有搶紅包或中獎,好比天天每一個人限量3個紅包,中那個等級的獎的個數等等。

 

此時咱們須要使用的方式就不是簡單的compareAndSet操做,由於它僅僅是考慮到物理上的併發,而不是在業務邏輯上去控制順序,此時咱們須要借鑑數據庫的事務序列號的一些思想來解決,假如每一個對象修改的次數能夠記住,修改前先對比下次數是否一致再修改,那麼這個問題就簡單了,AtomicStampedReference類正是提供這一功能的,其實它僅僅是在AtomicReference類的再一次包裝,裏面增長了一層引用和計數器,實際上是否爲計數器徹底由本身控制,大多數咱們是讓他自增的,你也能夠按照本身的方式來標示版本號,下面一個例子是ABA問題的簡單演示:

 

實例代碼3(ABA問題模擬代碼演示):

[java] view plain copy

 在CODE上查看代碼片派生到個人代碼片

  1. import java.util.concurrent.atomic.AtomicReference;  
  2.   
  3. /** 
  4.  * ABA問題模擬,線程併發中,致使ABA問題,解決方案是使用|AtomicMarkableReference 
  5.  * 請參看相應的例子:AtomicStampedReferenceTest、AtomicMarkableReferenceTest 
  6.  * 
  7.  */  
  8. public class AtomicReferenceABATest {  
  9.       
  10.     public final static AtomicReference <String>ATOMIC_REFERENCE = new AtomicReference<String>("abc");  
  11.   
  12.     public static void main(String []args) {  
  13.         for(int i = 0 ; i < 100 ; i++) {  
  14.             final int num = i;  
  15.             new Thread() {  
  16.                 public void run() {  
  17.                     try {  
  18.                         Thread.sleep(Math.abs((int)(Math.random() * 100)));  
  19.                     } catch (InterruptedException e) {  
  20.                         e.printStackTrace();  
  21.                     }  
  22.                     if(ATOMIC_REFERENCE.compareAndSet("abc" , "abc2")) {  
  23.                         System.out.println("我是線程:" + num + ",我得到了鎖進行了對象修改!");  
  24.                     }  
  25.                 }  
  26.             }.start();  
  27.         }  
  28.         new Thread() {  
  29.             public void run() {  
  30.                 while(!ATOMIC_REFERENCE.compareAndSet("abc2", "abc"));  
  31.                 System.out.println("已經改成原始值!");  
  32.             }  
  33.         }.start();  
  34.     }  
  35. }<strong>  
  36. </strong>  

代碼中和原來的例子,惟一的區別就是最後增長了一個線程讓他將數據修改成原來的值,並一直嘗試修改,直到修改爲功爲止,爲何沒有直接用:方法呢getAndSet方法呢,由於咱們的目的是要讓某個線程先將他修改成abc2後再讓他修改回abc,因此須要這樣作;

此時咱們獲得的結果是:

我是線程:41,我得到了鎖進行了對象修改!

已經改成原始值!

我是線程:85,我得到了鎖進行了對象修改!

固然你的線程編號多半和我不同,只要徵用到就對,能夠發現,有兩個線程修改了這個字符串,咱們是想那一堆將abc改爲abc2的線程僅有一個成功,即便其餘線程在他們徵用時將其修改成abc,也不能再修改。

 

 

此時咱們經過類來AtomicStampedReference解決這個問題:

實例代碼4(AtomicStampedReference解決ABA問題):

[java] view plain copy

 在CODE上查看代碼片派生到個人代碼片

  1. import java.util.concurrent.atomic.AtomicStampedReference;  
  2.   
  3. public class AtomicStampedReferenceTest {  
  4.       
  5.     public final static AtomicStampedReference <String>ATOMIC_REFERENCE = new AtomicStampedReference<String>("abc" , 0);  
  6.       
  7.     public static void main(String []args) {  
  8.         for(int i = 0 ; i < 100 ; i++) {  
  9.             final int num = i;  
  10.             final int stamp = ATOMIC_REFERENCE.getStamp();  
  11.             new Thread() {  
  12.                 public void run() {  
  13.                     try {  
  14.                         Thread.sleep(Math.abs((int)(Math.random() * 100)));  
  15.                     } catch (InterruptedException e) {  
  16.                         e.printStackTrace();  
  17.                     }  
  18.                     if(ATOMIC_REFERENCE.compareAndSet("abc" , "abc2" , stamp , stamp + 1)) {  
  19.                         System.out.println("我是線程:" + num + ",我得到了鎖進行了對象修改!");  
  20.                     }  
  21.                 }  
  22.             }.start();  
  23.         }  
  24.         new Thread() {  
  25.             public void run() {  
  26.                 int stamp = ATOMIC_REFERENCE.getStamp();  
  27.                 while(!ATOMIC_REFERENCE.compareAndSet("abc2", "abc" , stamp , stamp + 1));  
  28.                 System.out.println("已經改回爲原始值!");  
  29.             }  
  30.         }.start();  
  31.     }  
  32. }  


 

此時再運行程序看到的結果就是咱們想要的了,發現將abc修改成abc2的線程僅有一個被訪問,雖然被修改回了原始值,可是其餘線程也不會再將abc改成abc2。

 

而類:AtomicMarkableReferenceAtomicStampedReference功能差很少,有點區別的是:它描述更加簡單的是與否的關係,一般ABA問題只有兩種狀態,而AtomicStampedReference是多種狀態,那麼爲何還要有AtomicMarkableReference呢,由於它在處理是與否上面更加具備可讀性,而AtomicStampedReference過於隨意定義狀態,並不便於閱讀大量的是和否的關係,它能夠被認爲是一個計數器或狀態列表等信息,java提倡經過類名知道其意義,因此這個類的存在也是必要的,它的定義就是將數據變換爲true|false以下:

[java] view plain copy

 在CODE上查看代碼片派生到個人代碼片

  1. public final static AtomicMarkableReference <String>ATOMIC_MARKABLE_REFERENCE = new AtomicMarkableReference<String>("abc" , false);  

 

操做時使用:

[java] view plain copy

 在CODE上查看代碼片派生到個人代碼片

  1. ATOMIC_MARKABLE_REFERENCE.compareAndSet("abc", "abc2", false, true);  

 

好了,reference的三個類的種類都介紹了,咱們下面要開始說Atomic的數組用法,由於咱們開始說到的都是一些簡單變量和基本數據,操做數組呢?若是你來設計會怎麼設計,Atomic的數組要求不容許修改長度等,不像集合類那麼豐富的操做,不過它可讓你的數組上每一個元素的操做絕對安全的,也就是它細化的力度仍是到數組上的元素,爲你作了二次包裝,因此若是你來設計,就是在原有的操做上增長一個下標訪問便可,咱們來模擬一個Integer類型的數組,即:AtomicIntegerArray

 

實例代碼5(AtomicIntegerArrayTest.java)

[java] view plain copy

 在CODE上查看代碼片派生到個人代碼片

  1. import java.util.concurrent.atomic.AtomicIntegerArray;  
  2.   
  3. public class AtomicIntegerArrayTest {  
  4.   
  5.     /** 
  6.      * 常見的方法列表 
  7.      * @see AtomicIntegerArray#addAndGet(int, int) 執行加法,第一個參數爲數組的下標,第二個參數爲增長的數量,返回增長後的結果 
  8.      * @see AtomicIntegerArray#compareAndSet(int, int, int) 對比修改,參數1:數組下標,參數2:原始值,參數3,修改目標值,修改爲功返回true不然false 
  9.      * @see AtomicIntegerArray#decrementAndGet(int) 參數爲數組下標,將數組對應數字減小1,返回減小後的數據 
  10.      * @see AtomicIntegerArray#incrementAndGet(int) 參數爲數組下標,將數組對應數字增長1,返回增長後的數據 
  11.      *  
  12.      * @see AtomicIntegerArray#getAndAdd(int, int) 和addAndGet相似,區別是返回值是變化前的數據 
  13.      * @see AtomicIntegerArray#getAndDecrement(int) 和decrementAndGet相似,區別是返回變化前的數據 
  14.      * @see AtomicIntegerArray#getAndIncrement(int) 和incrementAndGet相似,區別是返回變化前的數據 
  15.      * @see AtomicIntegerArray#getAndSet(int, int) 將對應下標的數字設置爲指定值,第二個參數爲設置的值,返回是變化前的數據 
  16.      */  
  17.     private final static AtomicIntegerArray ATOMIC_INTEGER_ARRAY = new AtomicIntegerArray(new int[]{1,2,3,4,5,6,7,8,9,10});  
  18.       
  19.     public static void main(String []args) throws InterruptedException {  
  20.         Thread []threads = new Thread[100];  
  21.         for(int i = 0 ; i < 100 ; i++) {  
  22.             final int index = i % 10;  
  23.             final int threadNum = i;  
  24.             threads[i] = new Thread() {  
  25.                 public void run() {  
  26.                     int result = ATOMIC_INTEGER_ARRAY.addAndGet(index, index + 1);  
  27.                     System.out.println("線程編號爲:" + threadNum + " , 對應的原始值爲:" + (index + 1) + ",增長後的結果爲:" + result);  
  28.                 }  
  29.             };  
  30.             threads[i].start();  
  31.         }  
  32.         for(Thread thread : threads) {  
  33.             thread.join();  
  34.         }  
  35.         System.out.println("=========================>\n執行已經完成,結果列表:");  
  36.         for(int i = 0 ; i < ATOMIC_INTEGER_ARRAY.length() ; i++) {  
  37.             System.out.println(ATOMIC_INTEGER_ARRAY.get(i));  
  38.         }  
  39.     }  
  40. }  

 

計算結果說明:100個線程併發,每10個線程會被併發修改數組中的一個元素,也就是數組中的每一個元素會被10個線程併發修改訪問,每次增長原始值的大小,此時運算完的結果看最後輸出的敲好爲原始值的11倍數,和咱們預期的一致,若是不是線程安全那麼這個值什麼都有可能。

 

而相應的類:AtomicLongArray其實和AtomicIntegerArray操做方法相似,最大區別就是它操做的數據類型是long;而AtomicRerenceArray也是這樣,只是它方法只有兩個:

 

[java] view plain copy

 在CODE上查看代碼片派生到個人代碼片

  1. AtomicReferenceArray#compareAndSet(int, Object, Object)   
  2. 參數1:數組下標;  
  3. 參數2:修改原始值對比;  
  4. 參數3:修改目標值   
  5. 修改爲功返回true,不然返回false  
  6.   
  7. AtomicReferenceArray#getAndSet(int, Object)   
  8. 參數1:數組下標  
  9. 參數2:修改的目標  
  10. 修改爲功爲止,返回修改前的數據  

 

到這裏你是否對數組內部的操做應該有所瞭解了,和當初預期同樣,參數就是多了一個下標,爲了徹底驗證這點,跟蹤到源碼中能夠看到:

[java] view plain copy

 在CODE上查看代碼片派生到個人代碼片

  1. public final int addAndGet(int i, int delta) {  
  2.         while (true) {  
  3.             int current = get(i);  
  4.             int next = current + delta;  
  5.             if (compareAndSet(i, current, next))  
  6.                 return next;  
  7.         }  
  8.     }  

 

能夠看到根據get(i)獲取到對應的數據,而後作和普通AtomicInteger差很少的操做,get操做裏面有個細節是:

[java] view plain copy

 在CODE上查看代碼片派生到個人代碼片

  1. public final int get(int i) {  
  2.     return unsafe.getIntVolatile(array, rawIndex(i));  
  3. }  

這裏經過了unsafe獲取基於volatile方式獲取(可見性)獲取一個int類型的數據,而獲取的位置是由rawIndex來肯定,它的源碼是:

[java] view plain copy

 在CODE上查看代碼片派生到個人代碼片

  1. private long rawIndex(int i) {  
  2.     if (i < 0 || i >= array.length)  
  3.         throw new IndexOutOfBoundsException("index " + i);  
  4.     return base + (long) i * scale;  
  5. }  

 

能夠發現這個結果是一個地址位置,爲base加上一耳光偏移量,那麼看看base和scale的定義爲:

[java] view plain copy

 在CODE上查看代碼片派生到個人代碼片

  1. private static final int base = unsafe.arrayBaseOffset(int[].class);  
  2. private static final int scale = unsafe.arrayIndexScale(int[].class);  

 

能夠發現unsafe裏面提供了對數組base的位置的獲取,由於對象是有頭部的,而數組還有一個長度位置,第二個很明顯是一個數組元素所佔用的寬度,也就是基本精度;這裏應該能夠體會到unsafe所帶來的強大了吧。

 

本文最後要介紹的部分爲Updater也就是修改器,它算是Atomic的系列的一個擴展,Atomic系列是爲你定義好的一些對象,你可使用,可是若是是別人已經在使用的對象會原先的代碼須要修改成Atomic系列,此時若所有修改類型到對應的對象相信很麻煩,由於牽涉的代碼會不少,此時java提供一個外部的Updater能夠對對象的屬性自己的修改提供相似Atomic的操做,也就是它對這些普通的屬性的操做是併發下安全的,分別由:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceUpdater,這樣操做後,系統會更加靈活,也就是可能那些類的屬性只是在某些狀況下須要控制併發,不少時候不須要,可是他們的使用一般有如下幾個限制:

限制1:操做的目標不能是static類型,前面說到unsafe的已經能夠猜想到它提取的是非static類型的屬性偏移量,若是是static類型在獲取時若是沒有使用對應的方法是會報錯的,而這個Updater並無使用對應的方法。

限制2:操做的目標不能是final類型的,由於final根本無法修改。

限制3:必須是volatile類型的數據,也就是數據自己是讀一致的。

限制4:屬性必須對當前的Updater所在的區域是可見的,也就是private若是不是當前類確定是不可見的,protected若是不存在父子關係也是不可見的,default若是不是在同一個package下也是不可見的。

 

實現方式:經過反射找到屬性,對屬性進行操做,可是並非設置accessable,因此必須是可見的屬性才能操做。

 

說了這麼多,來個實例看看吧。

實例代碼6:(AtomicIntegerFieldUpdaterTest.java)

[java] view plain copy

 在CODE上查看代碼片派生到個人代碼片

  1. import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;  
  2.   
  3. public class AtomicIntegerFieldUpdaterTest {  
  4.   
  5.     static class A {  
  6.         volatile int intValue = 100;  
  7.     }  
  8.       
  9.     /** 
  10.      * 能夠直接訪問對應的變量,進行修改和處理 
  11.      * 條件:要在可訪問的區域內,若是是private或挎包訪問default類型以及非父親類的protected均沒法訪問到 
  12.      * 其次訪問對象不能是static類型的變量(由於在計算屬性的偏移量的時候沒法計算),也不能是final類型的變量(由於根本沒法修改),必須是普通的成員變量 
  13.      *  
  14.      * 方法(說明上和AtomicInteger幾乎一致,惟一的區別是第一個參數須要傳入對象的引用) 
  15.      * @see AtomicIntegerFieldUpdater#addAndGet(Object, int) 
  16.      * @see AtomicIntegerFieldUpdater#compareAndSet(Object, int, int) 
  17.      * @see AtomicIntegerFieldUpdater#decrementAndGet(Object) 
  18.      * @see AtomicIntegerFieldUpdater#incrementAndGet(Object) 
  19.      *  
  20.      * @see AtomicIntegerFieldUpdater#getAndAdd(Object, int) 
  21.      * @see AtomicIntegerFieldUpdater#getAndDecrement(Object) 
  22.      * @see AtomicIntegerFieldUpdater#getAndIncrement(Object) 
  23.      * @see AtomicIntegerFieldUpdater#getAndSet(Object, int) 
  24.      */  
  25.     public final static AtomicIntegerFieldUpdater <A>ATOMIC_INTEGER_UPDATER = AtomicIntegerFieldUpdater.newUpdater(A.class, "intValue");  
  26.       
  27.     public static void main(String []args) {  
  28.         final A a = new A();  
  29.         for(int i = 0 ; i < 100 ; i++) {  
  30.             final int num = i;  
  31.             new Thread() {  
  32.                 public void run() {  
  33.                     if(ATOMIC_INTEGER_UPDATER.compareAndSet(a, 100, 120)) {  
  34.                         System.out.println("我是線程:" + num + " 我對對應的值作了修改!");  
  35.                     }  
  36.                 }  
  37.             }.start();  
  38.         }  
  39.     }  
  40. }  

 

此時你會發現只有一個線程能夠對這個數據進行修改,其餘的方法如上面描述同樣,實現的功能和AtomicInteger相似。

AtomicLongFieldUpdater其實也是這樣,區別在於它所操做的數據是long類型。

AtomicReferenceFieldUpdater方法較少,主要是compareAndSet以及getAndSet兩個方法的使用,它的定義比數字類型的多一個參數以下:

[java] view plain copy

 在CODE上查看代碼片派生到個人代碼片

  1. static class A {  
  2.     volatile String stringValue = "abc";  
  3. }  
  4.   
  5.   
  6. AtomicReferenceFieldUpdater <A ,String>ATOMIC_REFERENCE_FIELD_UPDATER = AtomicReferenceFieldUpdater.newUpdater(A.class, String.class, "stringValue");  


 

能夠看到,這裏傳遞的參數增長了一個屬性的類型,由於引用的是一個對象,對象自己也有一個類型。

相關文章
相關標籤/搜索