Java JUC之Atomic係列12大類實例講解和原理分解
在java6以後我們不但接觸到了Lock相關的鎖,也接觸到了很多更加樂觀的原子修改操作,也就是在修改時我們隻需要保證它的那個瞬間是安全的即可,經過相應的包裝後可以再處理對象的並發修改,以及並發中的ABA問題,本文講述Atomic係列的類的實現以及使用方法,其中包含:
基本類:AtomicInteger、AtomicLong、AtomicBoolean;
引用類型:AtomicReference、AtomicReference的ABA實例、AtomicStampedRerence、AtomicMarkableReference;
數組類型:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
屬性原子修改器(Updater):AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater
看到這麼多類,你是否覺得很困惑,其實沒什麼,因為你隻需要看懂一個,其餘的方法和使用都是大同小異的,相關的類會介紹他們之間的區別在哪裏,在使用中需要注意的地方即可。
在使用Atomic係列前,我們需要先知道一個東西就是Unsafe類,全名為:sun.misc.Unsafe,這個類包含了大量的對C代碼的操作,包括很多直接內存分配以及原子操作的調用,而它之所以標記為非安全的,是告訴你這個裏麵大量的方法調用都會存在安全隱患,需要小心使用,否則會導致嚴重的後果,例如在通過unsafe分配內存的時候,如果自己指定某些區域可能會導致一些類似C++一樣的指針越界到其他進程的問題,不過它的具體使用並不是本文的重點,本文重點是Atomic係列的內容大多會基於unsafe類中的以下幾個本地方法來操作:
對象的引用進行對比後交換,交換成功返回true,交換失敗返回false,這個交換過程完全是原子的,在CPU上計算完結果後,都會對比內存的結果是否還是原先的值,若不是,則認為不能替換,因為變量是volatile類型所以最終寫入的數據會被其他線程看到,所以一個線程修改成功後,其他線程就發現自己修改失敗了。
參數1:對象所在的類本身的對象(一般這裏是對一個對象的屬性做修改,才會出現並發,所以該對象所存在的類也是有一個對象的)
參數2:這個屬性在這個對象裏麵的相對便宜量位置,其實對比時是對比內存單元,所以需要屬性的起始位置,而引用就是修改引用地址(根據OS、VM位數和參數配置決定寬度一般是4-8個字節),int就是修改相關的4個字節,而long就是修改相關的8個字節。
獲取偏移量也是通過unsafe的一個方法:objectFieldOffset(Fieldfield)來獲取屬性在對象中的偏移量;靜態變量需要通過:staticFieldOffset(Field field)獲取,調用的總方法是:fieldOffset(Fieldfield)
參數3:修改的引用的原始值,用於對比原來的引用和要修改的目標是否一致。
參數4:修改的目標值,要將數據修改成什麼。
public final native boolean compareAndSwapObject(Object paramObject1, long paramLong, Object paramObject2, Object paramObject3); public final native boolean compareAndSwapInt(Object paramObject, long paramLong, int paramInt1, int paramInt2);
#對long的操作,要看VM是否支持對Long的CAS,因為有可能VM本身不支持,若不支持,此時運算會變成Lock方式,不過現在VM都基本是支持的而已。
public final native boolean compareAndSwapLong(Object paramObject, long paramLong1, long paramLong2, long paramLong3);
我們不推薦直接使用unsafe來操作原子變量,而是通過java封裝好的一些類來操作原子變量。
實例代碼1:AtomicIntegerTest.java
import java.util.concurrent.atomic.AtomicInteger; public class AtomicIntegerTest { /** * 常見的方法列表 * @see AtomicInteger#get() 直接返回值 * @see AtomicInteger#getAndAdd(int) 增加指定的數據,返回變化前的數據 * @see AtomicInteger#getAndDecrement() 減少1,返回減少前的數據 * @see AtomicInteger#getAndIncrement() 增加1,返回增加前的數據 * @see AtomicInteger#getAndSet(int) 設置指定的數據,返回設置前的數據 * * @see AtomicInteger#addAndGet(int) 增加指定的數據後返回增加後的數據 * @see AtomicInteger#decrementAndGet() 減少1,返回減少後的值 * @see AtomicInteger#incrementAndGet() 增加1,返回增加後的值 * @see AtomicInteger#lazySet(int) 僅僅當get時才會set * * @see AtomicInteger#compareAndSet(int, int) 嚐試新增後對比,若增加成功則返回true否則返回false */ public final static AtomicInteger TEST_INTEGER = new AtomicInteger(1); public static void main(String []args) throws InterruptedException { final Thread []threads = new Thread[10]; for(int i = 0 ; i < 10 ; i++) { final int num = i; threads[i] = new Thread() { public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } int now = TEST_INTEGER.incrementAndGet(); System.out.println("我是線程:" + num + ",我得到值了,增加後的值為:" + now); } }; threads[i].start(); } for(Thread t : threads) { t.join(); } System.out.println("最終運行結果:" + TEST_INTEGER.get()); } }
代碼例子中模擬多個線程並發對AtomicInteger進行增加1的操作,如果這個數據是普通類型,那麼增加過程中出現的問題就是兩個線程可能同時看到的數據都是同一個數據,增加完成後寫回的時候,也是同一個數據,但是兩個加法應當串行增加1,也就是加2的操作,甚至於更加特殊的情況是一個線程加到3後,寫入,另一個線程寫入了2,還越變越少,也就是不能得到正確的結果,在並發下,我們模擬計數器,要得到精確的計數器值,就需要使用它,我們希望得到的結果是11,可以拷貝代碼進去運行後看到結果的確是11,順然輸出的順序可能不一樣,也同時可以證明線程的確是並發運行的(隻是在輸出的時候,征用System.out這個對象也不一定是誰先搶到),但是最終結果的確是11。
相信你對AtomicInteger的使用有一些了解了吧,要知道更多的方法使用,請參看這段代碼中定義變量位置的注釋,有關於AtomicInteger的相關方法的詳細注釋,可以直接跟蹤進去看源碼,注釋中使用了簡單的描述說明了方法的用途。
而對於AtomicLong呢,其實和AtomicInteger差不多,唯一的區別就是它處理的數據是long類型的就是了;
對於AtomicBoolean呢,方法要少一些,常見的方法就兩個:
AtomicBoolean#compareAndSet(boolean, boolean) 第一個參數為原始值,第二個參數為要修改的新值,若修改成功則返回true,否則返回false AtomicBoolean#getAndSet(boolean) 嚐試設置新的boolean值,直到成功為止,返回設置前的數據
因為boolean值就兩個值,所以就是來回改,相對的很多增加減少的方法自然就沒有了,對於使用來講,我們列舉一個boolean的並發修改,僅有一個線程可以修改成功的例子:
實例代碼2:AtomicBooleanTest.java
import java.util.concurrent.atomic.AtomicBoolean; public class AtomicBooleanTest { /** * 主要方法: * @see AtomicBoolean#compareAndSet(boolean, boolean) 第一個參數為原始值,第二個參數為要修改的新值,若修改成功則返回true,否則返回false * @see AtomicBoolean#getAndSet(boolean) 嚐試設置新的boolean值,直到成功為止,返回設置前的數據 */ public final static AtomicBoolean TEST_BOOLEAN = new AtomicBoolean(); public static void main(String []args) { for(int i = 0 ; i < 10 ; i++) { new Thread() { public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if(TEST_BOOLEAN.compareAndSet(false, true)) { System.out.println("我成功了!"); } } }.start(); } } }
這裏有10個線程,我們讓他們幾乎同時去征用boolean值的修改,修改成功者輸出:我成功了!此時你運行完你會發現隻會輸出一個“我成功了!”,說明征用過程中達到了鎖的效果。
那麼幾種基本類型就說完了,我們來看看裏麵的實現是不是如我們開始說的Unsafe那樣,看幾段源碼即可,我們看下AtomicInteger的一些源碼,例如開始用的:incrementAndGet方法,這個,它的源碼是:
public final int incrementAndGet() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return next; } }
可以看到內部有一個死循環,隻有不斷去做compareAndSet操作,直到成功為止,也就是修改的根本在compareAndSet方法裏麵,可以去看下相關的修改方法均是這樣實現,那麼看下compareAndSet方法的body部分是:
public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }
可以看到這裏使用了unsafe的compareAndSwapInt的方法,很明顯this就是指AtomicInteger當前的這個對象(這個對象不用像上麵說的它不能是static和final,它無所謂的),而valueOffset的定義是這樣的:
private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } }
可以看出是通過我們前麵所述的objectFieldOffset方法來獲取的屬性偏移量,所以你自己如果定義類似的操作的時候,就要注意,這個屬性不能是靜態的,否則不能用這個方法來獲取。
後麵兩個參數自然是對比值和需要修改的目標對象的地址。
其實Atomic係列你看到這裏,java層麵你就知道差不多了,其餘的就是特殊用法和包裝而已,剛才我們說了unsafe的3個方法無非是地址和值的區別在內存層麵是沒有本質區別的,因為地址本身也是數字值。
為了說明這個問題,我們就先說Reference的使用:
我們測試一個reference,和boolean測試方式一樣,也是測試多個線程隻有一個線程能修改它。
實例代碼1:AtomicReferenceTest.java
import java.util.concurrent.atomic.AtomicReference; public class AtomicReferenceTest { /** * 相關方法列表 * @see AtomicReference#compareAndSet(Object, Object) 對比設置值,參數1:原始值,參數2:修改目標引用 * @see AtomicReference#getAndSet(Object) 將引用的目標修改為設置的參數,直到修改成功為止,返回修改前的引用 */ public final static AtomicReference <String>ATOMIC_REFERENCE = new AtomicReference<String>("abc"); public static void main(String []args) { for(int i = 0 ; i < 100 ; i++) { final int num = i; new Thread() { public void run() { try { Thread.sleep(Math.abs((int)(Math.random() * 100))); } catch (InterruptedException e) { e.printStackTrace(); } if(ATOMIC_REFERENCE.compareAndSet("abc", new String("abc"))) { System.out.println("我是線程:" + num + ",我獲得了鎖進行了對象修改!"); } } }.start(); } } }
測試結果如我們所料,的確隻有一個線程,執行,跟著代碼:compareAndSet進去,發現源碼中的調用是:
public final boolean compareAndSet(V expect, V update) { return unsafe.compareAndSwapObject(this, valueOffset, expect, update); }
OK,的確和我們上麵所講一致,那麼此時我們又遇到了引用修改的新問題,什麼問題呢?ABA問題,什麼是ABA問題呢,當某些流程在處理過程中是順向的,也就是不允許重複處理的情況下,在某些情況下導致一個數據由A變成B,再中間可能經過0-N個環節後變成了A,此時A不允許再變成B了,因為此時的狀態已經發生了改變,例如:銀行資金裏麵做一批賬目操作,要求資金在80-100元的人,增加20元錢,時間持續一天,也就是後台程序會不斷掃描這些用戶的資金是否是在這個範圍,但是要求增加過的人就不能再增加了,如果增加20後,被人取出10元繼續在這個範圍,那麼就可以無限套現出來,就是ABA問題了,類似的還有搶紅包或中獎,比如每天每個人限量3個紅包,中那個等級的獎的個數等等。
此時我們需要使用的方式就不是簡單的compareAndSet操作,因為它僅僅是考慮到物理上的並發,而不是在業務邏輯上去控製順序,此時我們需要借鑒數據庫的事務序列號的一些思想來解決,假如每個對象修改的次數可以記住,修改前先對比下次數是否一致再修改,那麼這個問題就簡單了,AtomicStampedReference類正是提供這一功能的,其實它僅僅是在AtomicReference類的再一次包裝,裏麵增加了一層引用和計數器,其實是否為計數器完全由自己控製,大多數我們是讓他自增的,你也可以按照自己的方式來標示版本號,下麵一個例子是ABA問題的簡單演示:
實例代碼3(ABA問題模擬代碼演示):
import java.util.concurrent.atomic.AtomicReference; /** * ABA問題模擬,線程並發中,導致ABA問題,解決方案是使用|AtomicMarkableReference * 請參看相應的例子:AtomicStampedReferenceTest、AtomicMarkableReferenceTest * */ public class AtomicReferenceABATest { public final static AtomicReference <String>ATOMIC_REFERENCE = new AtomicReference<String>("abc"); public static void main(String []args) { for(int i = 0 ; i < 100 ; i++) { final int num = i; new Thread() { public void run() { try { Thread.sleep(Math.abs((int)(Math.random() * 100))); } catch (InterruptedException e) { e.printStackTrace(); } if(ATOMIC_REFERENCE.compareAndSet("abc" , "abc2")) { System.out.println("我是線程:" + num + ",我獲得了鎖進行了對象修改!"); } } }.start(); } new Thread() { public void run() { while(!ATOMIC_REFERENCE.compareAndSet("abc2", "abc")); System.out.println("已經改為原始值!"); } }.start(); } }
代碼中和原來的例子,唯一的區別就是最後增加了一個線程讓他將數據修改為原來的值,並一直嚐試修改,直到修改成功為止,為什麼沒有直接用:方法呢getAndSet方法呢,因為我們的目的是要讓某個線程先將他修改為abc2後再讓他修改回abc,所以需要這樣做;
此時我們得到的結果是:
我是線程:41,我獲得了鎖進行了對象修改!
已經改為原始值!
我是線程:85,我獲得了鎖進行了對象修改!
當然你的線程編號多半和我不一樣,隻要征用到就對,可以發現,有兩個線程修改了這個字符串,我們是想那一堆將abc改成abc2的線程僅有一個成功,即使其他線程在他們征用時將其修改為abc,也不能再修改。
此時我們通過類來AtomicStampedReference解決這個問題:
實例代碼4(AtomicStampedReference解決ABA問題):
import java.util.concurrent.atomic.AtomicStampedReference; public class AtomicStampedReferenceTest { public final static AtomicStampedReference <String>ATOMIC_REFERENCE = new AtomicStampedReference<String>("abc" , 0); public static void main(String []args) { for(int i = 0 ; i < 100 ; i++) { final int num = i; final int stamp = ATOMIC_REFERENCE.getStamp(); new Thread() { public void run() { try { Thread.sleep(Math.abs((int)(Math.random() * 100))); } catch (InterruptedException e) { e.printStackTrace(); } if(ATOMIC_REFERENCE.compareAndSet("abc" , "abc2" , stamp , stamp + 1)) { System.out.println("我是線程:" + num + ",我獲得了鎖進行了對象修改!"); } } }.start(); } new Thread() { public void run() { int stamp = ATOMIC_REFERENCE.getStamp(); while(!ATOMIC_REFERENCE.compareAndSet("abc2", "abc" , stamp , stamp + 1)); System.out.println("已經改回為原始值!"); } }.start(); } }
此時再運行程序看到的結果就是我們想要的了,發現將abc修改為abc2的線程僅有一個被訪問,雖然被修改回了原始值,但是其他線程也不會再將abc改為abc2。
而類:AtomicMarkableReference和AtomicStampedReference功能差不多,有點區別的是:它描述更加簡單的是與否的關係,通常ABA問題隻有兩種狀態,而AtomicStampedReference是多種狀態,那麼為什麼還要有AtomicMarkableReference呢,因為它在處理是與否上麵更加具有可讀性,而AtomicStampedReference過於隨意定義狀態,並不便於閱讀大量的是和否的關係,它可以被認為是一個計數器或狀態列表等信息,java提倡通過類名知道其意義,所以這個類的存在也是必要的,它的定義就是將數據變換為true|false如下:
public final static AtomicMarkableReference <String>ATOMIC_MARKABLE_REFERENCE = new AtomicMarkableReference<String>("abc" , false);
操作時使用:
ATOMIC_MARKABLE_REFERENCE.compareAndSet("abc", "abc2", false, true);
好了,reference的三個類的種類都介紹了,我們下麵要開始說Atomic的數組用法,因為我們開始說到的都是一些簡單變量和基本數據,操作數組呢?如果你來設計會怎麼設計,Atomic的數組要求不允許修改長度等,不像集合類那麼豐富的操作,不過它可以讓你的數組上每個元素的操作絕對安全的,也就是它細化的力度還是到數組上的元素,為你做了二次包裝,所以如果你來設計,就是在原有的操作上增加一個下標訪問即可,我們來模擬一個Integer類型的數組,即:AtomicIntegerArray
實例代碼5(AtomicIntegerArrayTest.java)
import java.util.concurrent.atomic.AtomicIntegerArray; public class AtomicIntegerArrayTest { /** * 常見的方法列表 * @see AtomicIntegerArray#addAndGet(int, int) 執行加法,第一個參數為數組的下標,第二個參數為增加的數量,返回增加後的結果 * @see AtomicIntegerArray#compareAndSet(int, int, int) 對比修改,參數1:數組下標,參數2:原始值,參數3,修改目標值,修改成功返回true否則false * @see AtomicIntegerArray#decrementAndGet(int) 參數為數組下標,將數組對應數字減少1,返回減少後的數據 * @see AtomicIntegerArray#incrementAndGet(int) 參數為數組下標,將數組對應數字增加1,返回增加後的數據 * * @see AtomicIntegerArray#getAndAdd(int, int) 和addAndGet類似,區別是返回值是變化前的數據 * @see AtomicIntegerArray#getAndDecrement(int) 和decrementAndGet類似,區別是返回變化前的數據 * @see AtomicIntegerArray#getAndIncrement(int) 和incrementAndGet類似,區別是返回變化前的數據 * @see AtomicIntegerArray#getAndSet(int, int) 將對應下標的數字設置為指定值,第二個參數為設置的值,返回是變化前的數據 */ private final static AtomicIntegerArray ATOMIC_INTEGER_ARRAY = new AtomicIntegerArray(new int[]{1,2,3,4,5,6,7,8,9,10}); public static void main(String []args) throws InterruptedException { Thread []threads = new Thread[100]; for(int i = 0 ; i < 100 ; i++) { final int index = i % 10; final int threadNum = i; threads[i] = new Thread() { public void run() { int result = ATOMIC_INTEGER_ARRAY.addAndGet(index, index + 1); System.out.println("線程編號為:" + threadNum + " , 對應的原始值為:" + (index + 1) + ",增加後的結果為:" + result); } }; threads[i].start(); } for(Thread thread : threads) { thread.join(); } System.out.println("=========================>\n執行已經完成,結果列表:"); for(int i = 0 ; i < ATOMIC_INTEGER_ARRAY.length() ; i++) { System.out.println(ATOMIC_INTEGER_ARRAY.get(i)); } } }
計算結果說明:100個線程並發,每10個線程會被並發修改數組中的一個元素,也就是數組中的每個元素會被10個線程並發修改訪問,每次增加原始值的大小,此時運算完的結果看最後輸出的敲好為原始值的11倍數,和我們預期的一致,如果不是線程安全那麼這個值什麼都有可能。
而相應的類:AtomicLongArray其實和AtomicIntegerArray操作方法類似,最大區別就是它操作的數據類型是long;而AtomicRerenceArray也是這樣,隻是它方法隻有兩個:
AtomicReferenceArray#compareAndSet(int, Object, Object) 參數1:數組下標; 參數2:修改原始值對比; 參數3:修改目標值 修改成功返回true,否則返回false AtomicReferenceArray#getAndSet(int, Object) 參數1:數組下標 參數2:修改的目標 修改成功為止,返回修改前的數據
到這裏你是否對數組內部的操作應該有所了解了,和當初預期一樣,參數就是多了一個下標,為了完全驗證這點,跟蹤到源碼中可以看到:
public final int addAndGet(int i, int delta) { while (true) { int current = get(i); int next = current + delta; if (compareAndSet(i, current, next)) return next; } }
可以看到根據get(i)獲取到對應的數據,然後做和普通AtomicInteger差不多的操作,get操作裏麵有個細節是:
public final int get(int i) { return unsafe.getIntVolatile(array, rawIndex(i)); }
這裏通過了unsafe獲取基於volatile方式獲取(可見性)獲取一個int類型的數據,而獲取的位置是由rawIndex來確定,它的源碼是:
private long rawIndex(int i) { if (i < 0 || i >= array.length) throw new IndexOutOfBoundsException("index " + i); return base + (long) i * scale; }
可以發現這個結果是一個地址位置,為base加上一耳光偏移量,那麼看看base和scale的定義為:
private static final int base = unsafe.arrayBaseOffset(int[].class); 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)
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; public class AtomicIntegerFieldUpdaterTest { static class A { volatile int intValue = 100; } /** * 可以直接訪問對應的變量,進行修改和處理 * 條件:要在可訪問的區域內,如果是private或挎包訪問default類型以及非父親類的protected均無法訪問到 * 其次訪問對象不能是static類型的變量(因為在計算屬性的偏移量的時候無法計算),也不能是final類型的變量(因為根本無法修改),必須是普通的成員變量 * * 方法(說明上和AtomicInteger幾乎一致,唯一的區別是第一個參數需要傳入對象的引用) * @see AtomicIntegerFieldUpdater#addAndGet(Object, int) * @see AtomicIntegerFieldUpdater#compareAndSet(Object, int, int) * @see AtomicIntegerFieldUpdater#decrementAndGet(Object) * @see AtomicIntegerFieldUpdater#incrementAndGet(Object) * * @see AtomicIntegerFieldUpdater#getAndAdd(Object, int) * @see AtomicIntegerFieldUpdater#getAndDecrement(Object) * @see AtomicIntegerFieldUpdater#getAndIncrement(Object) * @see AtomicIntegerFieldUpdater#getAndSet(Object, int) */ public final static AtomicIntegerFieldUpdater <A>ATOMIC_INTEGER_UPDATER = AtomicIntegerFieldUpdater.newUpdater(A.class, "intValue"); public static void main(String []args) { final A a = new A(); for(int i = 0 ; i < 100 ; i++) { final int num = i; new Thread() { public void run() { if(ATOMIC_INTEGER_UPDATER.compareAndSet(a, 100, 120)) { System.out.println("我是線程:" + num + " 我對對應的值做了修改!"); } } }.start(); } } }
此時你會發現隻有一個線程可以對這個數據進行修改,其他的方法如上麵描述一樣,實現的功能和AtomicInteger類似。
而AtomicLongFieldUpdater其實也是這樣,區別在於它所操作的數據是long類型。
AtomicReferenceFieldUpdater方法較少,主要是compareAndSet以及getAndSet兩個方法的使用,它的定義比數字類型的多一個參數如下:
static class A { volatile String stringValue = "abc"; } AtomicReferenceFieldUpdater <A ,String>ATOMIC_REFERENCE_FIELD_UPDATER = AtomicReferenceFieldUpdater.newUpdater(A.class, String.class, "stringValue");
可以看到,這裏傳遞的參數增加了一個屬性的類型,因為引用的是一個對象,對象本身也有一個類型。
最後更新:2017-04-04 07:03:49