http://blog.csdn.net/xieyuooo/article/details/8594713
在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:修改的目標值,要將數據修改成什么。
[java]?view plaincopy
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都基本是支持的而已。
[java]?view plaincopy
public?final?native?boolean?compareAndSwapLong(Object?paramObject,?long?paramLong1,?long?paramLong2,?long?paramLong3);??
我們不推薦直接使用unsafe來操作原子變量,而是通過java封裝好的一些類來操作原子變量。
實例代碼1:AtomicIntegerTest.java
[java]?view plaincopy
import?java.util.concurrent.atomic.AtomicInteger;?? public?class?AtomicIntegerTest?{?? ?? ????? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ????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());?? ????}?? }<strong>?? </strong>??
代碼例子中模擬多個線程并發對AtomicInteger進行增加1的操作,如果這個數據是普通類型,那么增加過程中出現的問題就是兩個線程可能同時看到的數據都是同一個數據,增加完成后寫回的時候,也是同一個數據,但是兩個加法應當串行增加1,也就是加2的操作,甚至于更加特殊的情況是一個線程加到3后,寫入,另一個線程寫入了2,還越變越少,也就是不能得到正確的結果,在并發下,我們模擬計數器,要得到精確的計數器值,就需要使用它,我們希望得到的結果是11,可以拷貝代碼進去運行后看到結果的確是11,順然輸出的順序可能不一樣,也同時可以證明線程的確是并發運行的(只是在輸出的時候,征用System.out這個對象也不一定是誰先搶到),但是最終結果的確是11。
?
相信你對AtomicInteger的使用有一些了解了吧,要知道更多的方法使用,請參看這段代碼中定義變量位置的注釋,有關于AtomicInteger的相關方法的詳細注釋,可以直接跟蹤進去看源碼,注釋中使用了簡單的描述說明了方法的用途。
?
而對于AtomicLong呢,其實和AtomicInteger差不多,唯一的區別就是它處理的數據是long類型的就是了;
對于AtomicBoolean呢,方法要少一些,常見的方法就兩個:
[java]?view plaincopy
AtomicBoolean#compareAndSet(boolean,?boolean)??第一個參數為原始值,第二個參數為要修改的新值,若修改成功則返回true,否則返回false?? AtomicBoolean#getAndSet(boolean)???嘗試設置新的boolean值,直到成功為止,返回設置前的數據??
因為boolean值就兩個值,所以就是來回改,相對的很多增加減少的方法自然就沒有了,對于使用來講,我們列舉一個boolean的并發修改,僅有一個線程可以修改成功的例子:
實例代碼2:AtomicBooleanTest.java
[java]?view plaincopy
import?java.util.concurrent.atomic.AtomicBoolean;?? ?? public?class?AtomicBooleanTest?{?? ?? ????? ? ? ? ?? ????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方法,這個,它的源碼是:
[java]?view plaincopy
public?final?int?incrementAndGet()?{?? ????for?(;;)?{?? ????????int?current?=?get();?? ????????int?next?=?current?+?1;?? ????????if?(compareAndSet(current,?next))?? ????????????return?next;?? ????}?? }??
可以看到內部有一個死循環,只有不斷去做compareAndSet操作,直到成功為止,也就是修改的根本在compareAndSet方法里面,可以去看下相關的修改方法均是這樣實現,那么看下compareAndSet方法的body部分是:
[java]?view plaincopy
public?final?boolean?compareAndSet(int?expect,?int?update)?{?? ????return?unsafe.compareAndSwapInt(this,?valueOffset,?expect,?update);?? }??
可以看到這里使用了unsafe的compareAndSwapInt的方法,很明顯this就是指AtomicInteger當前的這個對象(這個對象不用像上面說的它不能是static和final,它無所謂的),而valueOffset的定義是這樣的:
[java]?view plaincopy
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
[java]?view plaincopy
import?java.util.concurrent.atomic.AtomicReference;?? ?? public?class?AtomicReferenceTest?{?? ?? ????? ? ? ? ?? ????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進去,發現源碼中的調用是:
[java]?view plaincopy
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問題模擬代碼演示):
[java]?view plaincopy
import?java.util.concurrent.atomic.AtomicReference;?? ?? ? ? ? ? ? ?? 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();?? ????}?? }<strong>?? </strong>??
代碼中和原來的例子,唯一的區別就是最后增加了一個線程讓他將數據修改為原來的值,并一直嘗試修改,直到修改成功為止,為什么沒有直接用:方法呢getAndSet方法呢,因為我們的目的是要讓某個線程先將他修改為abc2后再讓他修改回abc,所以需要這樣做;
此時我們得到的結果是:
我是線程:41,我獲得了鎖進行了對象修改!
已經改為原始值!
我是線程:85,我獲得了鎖進行了對象修改!
當然你的線程編號多半和我不一樣,只要征用到就對,可以發現,有兩個線程修改了這個字符串,我們是想那一堆將abc改成abc2的線程僅有一個成功,即使其他線程在他們征用時將其修改為abc,也不能再修改。
?
?
此時我們通過類來AtomicStampedReference解決這個問題:
實例代碼4(AtomicStampedReference解決ABA問題,其原理是為每個元素封裝成一個box,<value, stamp>,也就是每個對象要指定一個stamp, 只有在對象地址相同并且stamp相同的情況下才能campareandset):
[java]?view plaincopy
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如下:
[java]?view plaincopy
public?final?static?AtomicMarkableReference?<String>ATOMIC_MARKABLE_REFERENCE?=?new?AtomicMarkableReference<String>("abc"?,?false);??
操作時使用:
[java]?view plaincopy
ATOMIC_MARKABLE_REFERENCE.compareAndSet("abc",?"abc2",?false,?true);??
好了,reference的三個類的種類都介紹了,我們下面要開始說Atomic的數組用法,因為我們開始說到的都是一些簡單變量和基本數據,操作數組呢?如果你來設計會怎么設計,Atomic的數組要求不允許修改長度等,不像集合類那么豐富的操作,不過它可以讓你的數組上每個元素的操作絕對安全的,也就是它細化的力度還是到數組上的元素,為你做了二次包裝,所以如果你來設計,就是在原有的操作上增加一個下標訪問即可,我們來模擬一個Integer類型的數組,即:AtomicIntegerArray
?
實例代碼5(AtomicIntegerArrayTest.java)
[java]?view plaincopy
import?java.util.concurrent.atomic.AtomicIntegerArray;?? ?? public?class?AtomicIntegerArrayTest?{?? ?? ????? ? ? ? ? ? ? ? ? ? ? ?? ????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也是這樣,只是它方法只有兩個:
[java]?view plaincopy
AtomicReferenceArray#compareAndSet(int,?Object,?Object)??? 參數1:數組下標;?? 參數2:修改原始值對比;?? 參數3:修改目標值??? 修改成功返回true,否則返回false?? ?? AtomicReferenceArray#getAndSet(int,?Object)??? 參數1:數組下標?? 參數2:修改的目標?? 修改成功為止,返回修改前的數據??
到這里你是否對數組內部的操作應該有所了解了,和當初預期一樣,參數就是多了一個下標,為了完全驗證這點,跟蹤到源碼中可以看到:
[java]?view plaincopy
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操作里面有個細節是:
[java]?view plaincopy
public?final?int?get(int?i)?{?? ????return?unsafe.getIntVolatile(array,?rawIndex(i));?? }??
這里通過了unsafe獲取基于volatile方式獲取(可見性)獲取一個int類型的數據,而獲取的位置是由rawIndex來確定,它的源碼是:
[java]?view plaincopy
private?long?rawIndex(int?i)?{?? ????if?(i?<?0?||?i?>=?array.length)?? ????????throw?new?IndexOutOfBoundsException("index?"?+?i);?? ????return?base?+?(long)?i?*?scale;?? }??
可以發現這個結果是一個地址位置,為base加上一耳光偏移量,那么看看base和scale的定義為:
[java]?view plaincopy
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)
[java]?view plaincopy
import?java.util.concurrent.atomic.AtomicIntegerFieldUpdater;?? ?? public?class?AtomicIntegerFieldUpdaterTest?{?? ?? ????static?class?A?{?? ????????volatile?int?intValue?=?100;?? ????}?? ?????? ????? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ????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兩個方法的使用,它的定義比數字類型的多一個參數如下:
[java]?view plaincopy
static?class?A?{?? ????volatile?String?stringValue?=?"abc";?? }?? ?? ?? AtomicReferenceFieldUpdater?<A?,String>ATOMIC_REFERENCE_FIELD_UPDATER?=?AtomicReferenceFieldUpdater.newUpdater(A.class,?String.class,?"stringValue");??
可以看到,這里傳遞的參數增加了一個屬性的類型,因為引用的是一個對象,對象本身也有一個類型。
總結
以上是生活随笔為你收集整理的Java JUC之Atomic系列12大类实例讲解和原理分解的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。