摘要:實(shí)戰(zhàn)高并發(fā)程序設(shè)計(jì)連載中的指針類和非常類似,不同之處就在于是對整數(shù)的封裝,而則對應(yīng)普通的對象引用。這樣,當(dāng)前線程就無法正確判斷這個(gè)對象究竟是否被修改過。摘自實(shí)戰(zhàn)高并發(fā)程序設(shè)計(jì)一書
【實(shí)戰(zhàn)Java高并發(fā)程序設(shè)計(jì)】連載1–Java中的指針:Unsafe類
AtomicReference和AtomicInteger非常類似,不同之處就在于AtomicInteger是對整數(shù)的封裝,而AtomicReference則對應(yīng)普通的對象引用。也就是它可以保證你在修改對象引用時(shí)的線程安全性。在介紹AtomicReference的同時(shí),我希望同時(shí)提出一個(gè)有關(guān)原子操作的邏輯上的不足。
之前我們說過,線程判斷被修改對象是否可以正確寫入的條件是對象的當(dāng)前值和期望是否一致。這個(gè)邏輯從一般意義上來說是正確的。但有可能出現(xiàn)一個(gè)小小的例外,就是當(dāng)你獲得對象當(dāng)前數(shù)據(jù)后,在準(zhǔn)備修改為新值前,對象的值被其他線程連續(xù)修改了2次,而經(jīng)過這2次修改后,對象的值又恢復(fù)為舊值。這樣,當(dāng)前線程就無法正確判斷這個(gè)對象究竟是否被修改過。如圖4.2所示,顯示了這種情況。
圖4.2 對象值被反復(fù)修改回原數(shù)據(jù)
一般來說,發(fā)生這種情況的概率很小。而且即使發(fā)生了,可能也不是什么大問題。比如,我們只是簡單得要做一個(gè)數(shù)值加法,即使在我取得期望值后,這個(gè)數(shù)字被不斷的修改,只要它最終改回了我的期望值,我的加法計(jì)算就不會出錯(cuò)。也就是說,當(dāng)你修改的對象沒有過程的狀態(tài)信息,所有的信息都只保存于對象的數(shù)值本身。
但是,在現(xiàn)實(shí)中,還可能存在另外一種場景。就是我們是否能修改對象的值,不僅取決于當(dāng)前值,還和對象的過程變化有關(guān),這時(shí),AtomicReference就無能為力了。
打一個(gè)比方,如果有一家蛋糕店,為了挽留客戶,絕對為貴賓卡里余額小于20元的客戶一次性贈送20元,刺激消費(fèi)者充值和消費(fèi)。但條件是,每一位客戶只能被贈送一次。
現(xiàn)在,我們就來模擬這個(gè)場景,為了演示AtomicReference,我在這里使用AtomicReference實(shí)現(xiàn)這個(gè)功能。首先,我們模擬用戶賬戶余額。
定義用戶賬戶余額:
static AtomicReferencemoney=newAtomicReference (); // 設(shè)置賬戶初始值小于20,顯然這是一個(gè)需要被充值的賬戶 money.set(19);
接著,我們需要若干個(gè)后臺線程,它們不斷掃描數(shù)據(jù),并為滿足條件的客戶充值。
01 //模擬多個(gè)線程同時(shí)更新后臺數(shù)據(jù)庫,為用戶充值 02 for(int i = 0 ; i < 3 ; i++) { 03 new Thread(){ 04 publicvoid run() { 05 while(true){ 06 while(true){ 07 Integer m=money.get(); 08 if(m<20){ 09 if(money.compareAndSet(m, m+20)){ 10 System.out.println("余額小于20元,充值成功,余額:"+money.get()+"元"); 11 break; 12 } 13 }else{ 14 //System.out.println("余額大于20元,無需充值"); 15 break ; 16 } 17 } 18 } 19 } 20 }.start(); 21 }
上述代碼第8行,判斷用戶余額并給予贈予金額。如果已經(jīng)被其他用戶處理,那么當(dāng)前線程就會失敗。因此,可以確保用戶只會被充值一次。
此時(shí),如果很不幸的,用戶正好正在進(jìn)行消費(fèi),就在贈予金額到賬的同時(shí),他進(jìn)行了一次消費(fèi),使得總金額又小于20元,并且正好累計(jì)消費(fèi)了20元。使得消費(fèi)、贈予后的金額等于消費(fèi)前、贈予前的金額。這時(shí),后臺的贈予進(jìn)程就會誤以為這個(gè)賬戶還沒有贈予,所以,存在被多次贈予的可能。下面,模擬了這個(gè)消費(fèi)線程:
01 //用戶消費(fèi)線程,模擬消費(fèi)行為 02 new Thread() { 03 public voidrun() { 04 for(inti=0;i<100;i++){ 05 while(true){ 06 Integer m=money.get(); 07 if(m>10){ 08 System.out.println("大于10元"); 09 if(money.compareAndSet(m, m-10)){ 10 System.out.println("成功消費(fèi)10元,余額:"+money.get()); 11 break; 12 } 13 }else{ 14 System.out.println("沒有足夠的金額"); 15 break; 16 } 17 } 18 try{Thread.sleep(100);} catch (InterruptedException e) {} 19 } 20 } 21 }.start();
述代碼中,消費(fèi)者只要貴賓卡里的錢大于10元,就會立即進(jìn)行一次10元的消費(fèi)。執(zhí)行上述程序,得到的輸出如下:
余額小于20元,充值成功,余額:39元 大于10元 成功消費(fèi)10元,余額:29 大于10元 成功消費(fèi)10元,余額:19 余額小于20元,充值成功,余額:39元 大于10元 成功消費(fèi)10元,余額:29 大于10元 成功消費(fèi)10元,余額:39 余額小于20元,充值成功,余額:39元
從這一段輸出中,可以看到,這個(gè)賬戶被先后反復(fù)多次充值。其原因正是因?yàn)橘~戶余額被反復(fù)修改,修改后的值等于原有的數(shù)值。使得CAS操作無法正確判斷當(dāng)前數(shù)據(jù)狀態(tài)。
雖然說這種情況出現(xiàn)的概率不大,但是依然是有可能的出現(xiàn)的。因此,當(dāng)業(yè)務(wù)上確實(shí)可能出現(xiàn)這種情況時(shí),我們也必須多加防范。體貼的JDK也已經(jīng)為我們考慮到了這種情況,使用AtomicStampedReference就可以很好的解決這個(gè)問題。
摘自《實(shí)戰(zhàn)Java高并發(fā)程序設(shè)計(jì)》一書
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/65492.html
摘要:在本例中,講述的無鎖來自于并發(fā)包我們將這個(gè)無鎖的稱為。在這里,我們使用二維數(shù)組來表示的內(nèi)部存儲,如下變量存放所有的內(nèi)部元素。為什么使用二維數(shù)組去實(shí)現(xiàn)一個(gè)一維的呢這是為了將來進(jìn)行動(dòng)態(tài)擴(kuò)展時(shí)可以更加方便。 我們已經(jīng)比較完整得介紹了有關(guān)無鎖的概念和使用方法。相對于有鎖的方法,使用無鎖的方式編程更加考驗(yàn)一個(gè)程序員的耐心和智力。但是,無鎖帶來的好處也是顯而易見的,第一,在高并發(fā)的情況下,它比有鎖...
摘要:有時(shí)候,由于初期考慮不周,或者后期的需求變化,一些普通變量可能也會有線程安全的需求。它可以讓你在不改動(dòng)或者極少改動(dòng)原有代碼的基礎(chǔ)上,讓普通的變量也享受操作帶來的線程安全性,這樣你可以修改極少的代碼,來獲得線程安全的保證。 有時(shí)候,由于初期考慮不周,或者后期的需求變化,一些普通變量可能也會有線程安全的需求。如果改動(dòng)不大,我們可以簡單地修改程序中每一個(gè)使用或者讀取這個(gè)變量的地方。但顯然,這...
摘要:公平鎖非公平鎖公平鎖公平鎖是指多個(gè)線程按照申請鎖的順序來獲取鎖。加鎖后,任何其他試圖再次加鎖的線程會被阻塞,直到當(dāng)前進(jìn)程解鎖。重量級鎖會讓其他申請的線程進(jìn)入阻塞,性能降低。 Java 中15種鎖的介紹 在讀很多并發(fā)文章中,會提及各種各樣鎖如公平鎖,樂觀鎖等等,這篇文章介紹各種鎖的分類。介紹的內(nèi)容如下: 公平鎖 / 非公平鎖 可重入鎖 / 不可重入鎖 獨(dú)享鎖 / 共享鎖 互斥鎖 / 讀...
摘要:但是,有些操作會依賴于對象的變化過程,此時(shí)的解決思路一般就是使用版本號。在變量前面追加上版本號,每次變量更新的時(shí)候把版本號加一,那么就會變成。四的引入就是上面所說的加了版本號的。 showImg(https://segmentfault.com/img/remote/1460000016012188); 本文首發(fā)于一世流云的專欄:https://segmentfault.com/blo...
摘要:并發(fā)包將這種無鎖方案封裝提煉之后,實(shí)現(xiàn)了一系列的原子類。無鎖方案相對互斥鎖方案,最大的好處就是性能。作為一條指令,指令本身是能夠保證原子性的。 前面我們多次提到一個(gè)累加器的例子,示例代碼如下。在這個(gè)例子中,add10K() 這個(gè)方法不是線程安全的,問題就出在變量 count 的可見性和 count+=1 的原子性上。可見性問題可以用 volatile 來解決,而原子性問題我們前面一直都...
閱讀 962·2019-08-30 15:55
閱讀 556·2019-08-26 13:56
閱讀 2086·2019-08-26 12:23
閱讀 3306·2019-08-26 10:29
閱讀 608·2019-08-26 10:17
閱讀 2874·2019-08-23 16:53
閱讀 705·2019-08-23 15:55
閱讀 2831·2019-08-23 14:25