【實戰Java高併發程序設計 3】帶有時間戳的對象引用:AtomicStampedReference


    AtomicReference沒法解決上述問題的根本是由於對象在修改過程當中,丟失了狀態信息。對象值自己與狀態被畫上了等號。所以,咱們只要可以記錄對象在修改過程當中的狀態值,就能夠很好的解決對象被反覆修改致使線程沒法正確判斷對象狀態的問題。
html

      AtomicStampedReference正是這麼作的。它內部不只維護了對象值,還維護了一個時間戳(我這裏把它稱爲時間戳,實際上它可使任何一個整數,它使用整數來表示狀態值)。當AtomicStampedReference對應的數值被修改時,除了更新數據自己外,還必需要更新時間戳。當AtomicStampedReference設置對象值時,對象值以及時間戳都必須知足指望值,寫入纔會成功。所以,即便對象值被反覆讀寫,寫回原值,只要時間戳發生變化,就能防止不恰當的寫入。java

AtomicStampedReference的幾個API在AtomicReference的基礎上新增了有關時間戳的信息:數據庫

//比較設置 參數依次爲:指望值 寫入新值 指望時間戳 新時間戳
public boolean compareAndSet(V expectedReference,V  
newReference,int expectedStamp,int newStamp)
//得到當前對象引用
public V getReference()
//得到當前時間戳
public int getStamp()
//設置當前對象引用和時間戳
public void set(V newReference, int newStamp)


有了AtomicStampedReference這個法寶,咱們就不再用擔憂對象被寫壞啦!如今,就讓咱們使用AtomicStampedReference在修正那個貴賓卡充值的問題的:併發

01 public class AtomicStampedReferenceDemo {
02 static AtomicStampedReference<Integer> money=new AtomicStampedReference<Integer>(19,0);
03    public staticvoid main(String[] args) {
04        //模擬多個線程同時更新後臺數據庫,爲用戶充值
05        for(int i = 0 ; i < 3 ; i++) {
06            final int timestamp=money.getStamp();
07             newThread() {  
08                public void run() { 
09                    while(true){
10                        while(true){
11                             Integerm=money.getReference();
12                             if(m<20){
13                          if(money.compareAndSet(m,m+20,timestamp,timestamp+1)){
14           System.out.println("餘額小於20元,充值成功,餘額:"+money.getReference()+"元");
15                                     break;
16                                 }
17                             }else{
18                                //System.out.println("餘額大於20元,無需充值");
19                                 break ;
20                             }
21                        }
22                    }
23                } 
24            }.start();
25         }
26        
27        //用戶消費線程,模擬消費行爲
28        new Thread() { 
29             publicvoid run() { 
30                for(int i=0;i<100;i++){
31                    while(true){
32                        int timestamp=money.getStamp();
33                        Integer m=money.getReference();
34                        if(m>10){
35                             System.out.println("大於10元");
36                          if(money.compareAndSet(m, m-10,timestamp,timestamp+1)){
37                       System.out.println("成功消費10元,餘額:"+money.getReference());
38                                 break;
39                             }
40                        }else{
41                            System.out.println("沒有足夠的金額");
42                             break;
43                        }
44                    }
45                    try {Thread.sleep(100);} catch (InterruptedException e) {}
46                 }
47             } 
48        }.start(); 
49    }
50 }

第2行,咱們使用AtomicStampedReference代替原來的AtomicReference。第6行得到帳戶的時間戳。後續的贈予操做以這個時間戳爲依據。若是贈予成功(13行),則修改時間戳。使得系統不可能發生二次贈予的狀況。消費線程也是相似,每次操做,都使得時間戳加1(36行),使之不可能重複。高併發

執行上述代碼,能夠獲得如下輸出:spa

餘額小於20元,充值成功,餘額:39元
大於10元
成功消費10元,餘額:29
大於10元
成功消費10元,餘額:19
大於10元
成功消費10元,餘額:9
沒有足夠的金額
能夠看到,帳戶只被贈予了一次。



摘自:實戰Java高併發程序設計
線程

相關文章
相關標籤/搜索