http://blog.csdn.net/xieyuooo/article/details/8594713
在java6以后我們不但接觸到了Lock相關(guān)的鎖,也接觸到了很多更加樂觀的原子修改操作,也就是在修改時我們只需要保證它的那個瞬間是安全的即可,經(jīng)過相應(yīng)的包裝后可以再處理對象的并發(fā)修改,以及并發(fā)中的ABA問題,本文講述Atomic系列的類的實現(xiàn)以及使用方法,其中包含:
基本類:AtomicInteger、AtomicLong、AtomicBoolean;
引用類型:AtomicReference、AtomicReference的ABA實例、AtomicStampedRerence、AtomicMarkableReference;
數(shù)組類型:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
屬性原子修改器(Updater):AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater
?
看到這么多類,你是否覺得很困惑,其實沒什么,因為你只需要看懂一個,其余的方法和使用都是大同小異的,相關(guān)的類會介紹他們之間的區(qū)別在哪里,在使用中需要注意的地方即可。
?
在使用Atomic系列前,我們需要先知道一個東西就是Unsafe類,全名為:sun.misc.Unsafe,這個類包含了大量的對C代碼的操作,包括很多直接內(nèi)存分配以及原子操作的調(diào)用,而它之所以標(biāo)記為非安全的,是告訴你這個里面大量的方法調(diào)用都會存在安全隱患,需要小心使用,否則會導(dǎo)致嚴(yán)重的后果,例如在通過unsafe分配內(nèi)存的時候,如果自己指定某些區(qū)域可能會導(dǎo)致一些類似C++一樣的指針越界到其他進程的問題,不過它的具體使用并不是本文的重點,本文重點是Atomic系列的內(nèi)容大多會基于unsafe類中的以下幾個本地方法來操作:
?
對象的引用進行對比后交換,交換成功返回true,交換失敗返回false,這個交換過程完全是原子的,在CPU上計算完結(jié)果后,都會對比內(nèi)存的結(jié)果是否還是原先的值,若不是,則認(rèn)為不能替換,因為變量是volatile類型所以最終寫入的數(shù)據(jù)會被其他線程看到,所以一個線程修改成功后,其他線程就發(fā)現(xiàn)自己修改失敗了。
參數(shù)1:對象所在的類本身的對象(一般這里是對一個對象的屬性做修改,才會出現(xiàn)并發(fā),所以該對象所存在的類也是有一個對象的)
參數(shù)2:這個屬性在這個對象里面的相對便宜量位置,其實對比時是對比內(nèi)存單元,所以需要屬性的起始位置,而引用就是修改引用地址(根據(jù)OS、VM位數(shù)和參數(shù)配置決定寬度一般是4-8個字節(jié)),int就是修改相關(guān)的4個字節(jié),而long就是修改相關(guān)的8個字節(jié)。
獲取偏移量也是通過unsafe的一個方法:objectFieldOffset(Fieldfield)來獲取屬性在對象中的偏移量;靜態(tài)變量需要通過:staticFieldOffset(Field field)獲取,調(diào)用的總方法是:fieldOffset(Fieldfield)
參數(shù)3:修改的引用的原始值,用于對比原來的引用和要修改的目標(biāo)是否一致。
參數(shù)4:修改的目標(biāo)值,要將數(shù)據(jù)修改成什么。
[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方式,不過現(xiàn)在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("最終運行結(jié)果:"?+?TEST_INTEGER.get());?? ????}?? }<strong>?? </strong>??
代碼例子中模擬多個線程并發(fā)對AtomicInteger進行增加1的操作,如果這個數(shù)據(jù)是普通類型,那么增加過程中出現(xiàn)的問題就是兩個線程可能同時看到的數(shù)據(jù)都是同一個數(shù)據(jù),增加完成后寫回的時候,也是同一個數(shù)據(jù),但是兩個加法應(yīng)當(dāng)串行增加1,也就是加2的操作,甚至于更加特殊的情況是一個線程加到3后,寫入,另一個線程寫入了2,還越變越少,也就是不能得到正確的結(jié)果,在并發(fā)下,我們模擬計數(shù)器,要得到精確的計數(shù)器值,就需要使用它,我們希望得到的結(jié)果是11,可以拷貝代碼進去運行后看到結(jié)果的確是11,順然輸出的順序可能不一樣,也同時可以證明線程的確是并發(fā)運行的(只是在輸出的時候,征用System.out這個對象也不一定是誰先搶到),但是最終結(jié)果的確是11。
?
相信你對AtomicInteger的使用有一些了解了吧,要知道更多的方法使用,請參看這段代碼中定義變量位置的注釋,有關(guān)于AtomicInteger的相關(guān)方法的詳細(xì)注釋,可以直接跟蹤進去看源碼,注釋中使用了簡單的描述說明了方法的用途。
?
而對于AtomicLong呢,其實和AtomicInteger差不多,唯一的區(qū)別就是它處理的數(shù)據(jù)是long類型的就是了;
對于AtomicBoolean呢,方法要少一些,常見的方法就兩個:
[java]?view plaincopy
AtomicBoolean#compareAndSet(boolean,?boolean)??第一個參數(shù)為原始值,第二個參數(shù)為要修改的新值,若修改成功則返回true,否則返回false?? AtomicBoolean#getAndSet(boolean)???嘗試設(shè)置新的boolean值,直到成功為止,返回設(shè)置前的數(shù)據(jù)??
因為boolean值就兩個值,所以就是來回改,相對的很多增加減少的方法自然就沒有了,對于使用來講,我們列舉一個boolean的并發(fā)修改,僅有一個線程可以修改成功的例子:
實例代碼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值的修改,修改成功者輸出:我成功了!此時你運行完你會發(fā)現(xiàn)只會輸出一個“我成功了!”,說明征用過程中達到了鎖的效果。
?
?
那么幾種基本類型就說完了,我們來看看里面的實現(xiàn)是不是如我們開始說的Unsafe那樣,看幾段源碼即可,我們看下AtomicInteger的一些源碼,例如開始用的:incrementAndGet方法,這個,它的源碼是:
[java]?view plaincopy
public?final?int?incrementAndGet()?{?? ????for?(;;)?{?? ????????int?current?=?get();?? ????????int?next?=?current?+?1;?? ????????if?(compareAndSet(current,?next))?? ????????????return?next;?? ????}?? }??
可以看到內(nèi)部有一個死循環(huán),只有不斷去做compareAndSet操作,直到成功為止,也就是修改的根本在compareAndSet方法里面,可以去看下相關(guān)的修改方法均是這樣實現(xiàn),那么看下compareAndSet方法的body部分是:
[java]?view plaincopy
public?final?boolean?compareAndSet(int?expect,?int?update)?{?? ????return?unsafe.compareAndSwapInt(this,?valueOffset,?expect,?update);?? }??
可以看到這里使用了unsafe的compareAndSwapInt的方法,很明顯this就是指AtomicInteger當(dāng)前的這個對象(這個對象不用像上面說的它不能是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方法來獲取的屬性偏移量,所以你自己如果定義類似的操作的時候,就要注意,這個屬性不能是靜態(tài)的,否則不能用這個方法來獲取。
?
后面兩個參數(shù)自然是對比值和需要修改的目標(biāo)對象的地址。
其實Atomic系列你看到這里,java層面你就知道差不多了,其余的就是特殊用法和包裝而已,剛才我們說了unsafe的3個方法無非是地址和值的區(qū)別在內(nèi)存層面是沒有本質(zhì)區(qū)別的,因為地址本身也是數(shù)字值。
?
為了說明這個問題,我們就先說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();?? ????????}?? ????}?? }??
測試結(jié)果如我們所料,的確只有一個線程,執(zhí)行,跟著代碼:compareAndSet進去,發(fā)現(xiàn)源碼中的調(diào)用是:
[java]?view plaincopy
public?final?boolean?compareAndSet(V?expect,?V?update)?{?? ????return?unsafe.compareAndSwapObject(this,?valueOffset,?expect,?update);?? }??
OK,的確和我們上面所講一致,那么此時我們又遇到了引用修改的新問題,什么問題呢?ABA問題,什么是ABA問題呢,當(dāng)某些流程在處理過程中是順向的,也就是不允許重復(fù)處理的情況下,在某些情況下導(dǎo)致一個數(shù)據(jù)由A變成B,再中間可能經(jīng)過0-N個環(huán)節(jié)后變成了A,此時A不允許再變成B了,因為此時的狀態(tài)已經(jīng)發(fā)生了改變,例如:銀行資金里面做一批賬目操作,要求資金在80-100元的人,增加20元錢,時間持續(xù)一天,也就是后臺程序會不斷掃描這些用戶的資金是否是在這個范圍,但是要求增加過的人就不能再增加了,如果增加20后,被人取出10元繼續(xù)在這個范圍,那么就可以無限套現(xiàn)出來,就是ABA問題了,類似的還有搶紅包或中獎,比如每天每個人限量3個紅包,中那個等級的獎的個數(shù)等等。
?
此時我們需要使用的方式就不是簡單的compareAndSet操作,因為它僅僅是考慮到物理上的并發(fā),而不是在業(yè)務(wù)邏輯上去控制順序,此時我們需要借鑒數(shù)據(jù)庫的事務(wù)序列號的一些思想來解決,假如每個對象修改的次數(shù)可以記住,修改前先對比下次數(shù)是否一致再修改,那么這個問題就簡單了,AtomicStampedReference類正是提供這一功能的,其實它僅僅是在AtomicReference類的再一次包裝,里面增加了一層引用和計數(shù)器,其實是否為計數(shù)器完全由自己控制,大多數(shù)我們是讓他自增的,你也可以按照自己的方式來標(biāo)示版本號,下面一個例子是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("已經(jīng)改為原始值!");?? ????????????}?? ????????}.start();?? ????}?? }<strong>?? </strong>??
代碼中和原來的例子,唯一的區(qū)別就是最后增加了一個線程讓他將數(shù)據(jù)修改為原來的值,并一直嘗試修改,直到修改成功為止,為什么沒有直接用:方法呢getAndSet方法呢,因為我們的目的是要讓某個線程先將他修改為abc2后再讓他修改回abc,所以需要這樣做;
此時我們得到的結(jié)果是:
我是線程:41,我獲得了鎖進行了對象修改!
已經(jīng)改為原始值!
我是線程:85,我獲得了鎖進行了對象修改!
當(dāng)然你的線程編號多半和我不一樣,只要征用到就對,可以發(fā)現(xiàn),有兩個線程修改了這個字符串,我們是想那一堆將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("已經(jīng)改回為原始值!");?? ????????????}?? ????????}.start();?? ????}?? }??
此時再運行程序看到的結(jié)果就是我們想要的了,發(fā)現(xiàn)將abc修改為abc2的線程僅有一個被訪問,雖然被修改回了原始值,但是其他線程也不會再將abc改為abc2。
?
而類:AtomicMarkableReference和AtomicStampedReference功能差不多,有點區(qū)別的是:它描述更加簡單的是與否的關(guān)系,通常ABA問題只有兩種狀態(tài),而AtomicStampedReference是多種狀態(tài),那么為什么還要有AtomicMarkableReference呢,因為它在處理是與否上面更加具有可讀性,而AtomicStampedReference過于隨意定義狀態(tài),并不便于閱讀大量的是和否的關(guān)系,它可以被認(rèn)為是一個計數(shù)器或狀態(tài)列表等信息,java提倡通過類名知道其意義,所以這個類的存在也是必要的,它的定義就是將數(shù)據(jù)變換為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的數(shù)組用法,因為我們開始說到的都是一些簡單變量和基本數(shù)據(jù),操作數(shù)組呢?如果你來設(shè)計會怎么設(shè)計,Atomic的數(shù)組要求不允許修改長度等,不像集合類那么豐富的操作,不過它可以讓你的數(shù)組上每個元素的操作絕對安全的,也就是它細(xì)化的力度還是到數(shù)組上的元素,為你做了二次包裝,所以如果你來設(shè)計,就是在原有的操作上增加一個下標(biāo)訪問即可,我們來模擬一個Integer類型的數(shù)組,即: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?+?"?,?對應(yīng)的原始值為:"?+?(index?+?1)?+?",增加后的結(jié)果為:"?+?result);?? ????????????????}?? ????????????};?? ????????????threads[i].start();?? ????????}?? ????????for(Thread?thread?:?threads)?{?? ????????????thread.join();?? ????????}?? ????????System.out.println("=========================>\n執(zhí)行已經(jīng)完成,結(jié)果列表:");?? ????????for(int?i?=?0?;?i?<?ATOMIC_INTEGER_ARRAY.length()?;?i++)?{?? ????????????System.out.println(ATOMIC_INTEGER_ARRAY.get(i));?? ????????}?? ????}?? }??
計算結(jié)果說明:100個線程并發(fā),每10個線程會被并發(fā)修改數(shù)組中的一個元素,也就是數(shù)組中的每個元素會被10個線程并發(fā)修改訪問,每次增加原始值的大小,此時運算完的結(jié)果看最后輸出的敲好為原始值的11倍數(shù),和我們預(yù)期的一致,如果不是線程安全那么這個值什么都有可能。
而相應(yīng)的類:AtomicLongArray其實和AtomicIntegerArray操作方法類似,最大區(qū)別就是它操作的數(shù)據(jù)類型是long;而AtomicRerenceArray也是這樣,只是它方法只有兩個:
[java]?view plaincopy
AtomicReferenceArray#compareAndSet(int,?Object,?Object)??? 參數(shù)1:數(shù)組下標(biāo);?? 參數(shù)2:修改原始值對比;?? 參數(shù)3:修改目標(biāo)值??? 修改成功返回true,否則返回false?? ?? AtomicReferenceArray#getAndSet(int,?Object)??? 參數(shù)1:數(shù)組下標(biāo)?? 參數(shù)2:修改的目標(biāo)?? 修改成功為止,返回修改前的數(shù)據(jù)??
到這里你是否對數(shù)組內(nèi)部的操作應(yīng)該有所了解了,和當(dāng)初預(yù)期一樣,參數(shù)就是多了一個下標(biāo),為了完全驗證這點,跟蹤到源碼中可以看到:
[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;?? ????????}?? ????}??
可以看到根據(jù)get(i)獲取到對應(yīng)的數(shù)據(jù),然后做和普通AtomicInteger差不多的操作,get操作里面有個細(xì)節(jié)是:
[java]?view plaincopy
public?final?int?get(int?i)?{?? ????return?unsafe.getIntVolatile(array,?rawIndex(i));?? }??
這里通過了unsafe獲取基于volatile方式獲取(可見性)獲取一個int類型的數(shù)據(jù),而獲取的位置是由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;?? }??
可以發(fā)現(xiàn)這個結(jié)果是一個地址位置,為base加上一耳光偏移量,那么看看base和scale的定義為:
[java]?view plaincopy
private?static?final?int?base?=?unsafe.arrayBaseOffset(int[].class);?? private?static?final?int?scale?=?unsafe.arrayIndexScale(int[].class);??
可以發(fā)現(xiàn)unsafe里面提供了對數(shù)組base的位置的獲取,因為對象是有頭部的,而數(shù)組還有一個長度位置,第二個很明顯是一個數(shù)組元素所占用的寬度,也就是基本精度;這里應(yīng)該可以體會到unsafe所帶來的強大了吧。
?
本文最后要介紹的部分為Updater也就是修改器,它算是Atomic的系列的一個擴展,Atomic系列是為你定義好的一些對象,你可以使用,但是如果是別人已經(jīng)在使用的對象會原先的代碼需要修改為Atomic系列,此時若全部修改類型到對應(yīng)的對象相信很麻煩,因為牽涉的代碼會很多,此時java提供一個外部的Updater可以對對象的屬性本身的修改提供類似Atomic的操作,也就是它對這些普通的屬性的操作是并發(fā)下安全的,分別由:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceUpdater,這樣操作后,系統(tǒng)會更加靈活,也就是可能那些類的屬性只是在某些情況下需要控制并發(fā),很多時候不需要,但是他們的使用通常有以下幾個限制:
限制1:操作的目標(biāo)不能是static類型,前面說到unsafe的已經(jīng)可以猜測到它提取的是非static類型的屬性偏移量,如果是static類型在獲取時如果沒有使用對應(yīng)的方法是會報錯的,而這個Updater并沒有使用對應(yīng)的方法。
限制2:操作的目標(biāo)不能是final類型的,因為final根本沒法修改。
限制3:必須是volatile類型的數(shù)據(jù),也就是數(shù)據(jù)本身是讀一致的。
限制4:屬性必須對當(dāng)前的Updater所在的區(qū)域是可見的,也就是private如果不是當(dāng)前類肯定是不可見的,protected如果不存在父子關(guān)系也是不可見的,default如果不是在同一個package下也是不可見的。
?
實現(xiàn)方式:通過反射找到屬性,對屬性進行操作,但是并不是設(shè)置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?+?"?我對對應(yīng)的值做了修改!");?? ????????????????????}?? ????????????????}?? ????????????}.start();?? ????????}?? ????}?? }??
此時你會發(fā)現(xiàn)只有一個線程可以對這個數(shù)據(jù)進行修改,其他的方法如上面描述一樣,實現(xiàn)的功能和AtomicInteger類似。
而AtomicLongFieldUpdater其實也是這樣,區(qū)別在于它所操作的數(shù)據(jù)是long類型。
AtomicReferenceFieldUpdater方法較少,主要是compareAndSet以及getAndSet兩個方法的使用,它的定義比數(shù)字類型的多一個參數(shù)如下:
[java]?view plaincopy
static?class?A?{?? ????volatile?String?stringValue?=?"abc";?? }?? ?? ?? AtomicReferenceFieldUpdater?<A?,String>ATOMIC_REFERENCE_FIELD_UPDATER?=?AtomicReferenceFieldUpdater.newUpdater(A.class,?String.class,?"stringValue");??
可以看到,這里傳遞的參數(shù)增加了一個屬性的類型,因為引用的是一個對象,對象本身也有一個類型。
總結(jié)
以上是生活随笔為你收集整理的Java JUC之Atomic系列12大类实例讲解和原理分解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。