【實戰Java高併發程序設計5】讓普通變量也享受原子操做

有時候,因爲初期考慮不周,或者後期的需求變化,一些普通變量可能也會有線程安全的需求。若是改動不大,咱們能夠簡單地修改程序中每個使用或者讀取這個變量的地方。但顯然,這樣並不符合軟件設計中的一條重要原則——開閉原則。也就是系統對功能的增長應該是開發的,而對修改應該是相對保守的。並且,若是系統裏使用到這個變量的地方特別多,一個一個修改也是一件使人厭煩的事情(何況不少使用場景下可能只是只讀的,並沒有線程安全的強烈要求,徹底能夠保持原樣)。segmentfault

若是你有這種困擾,在這裏根本不須要擔憂,由於在原子包裏還有一個實用的工具類AtomicIntegerFieldUpdater。它可讓你在不改動(或者極少改動)原有代碼的基礎上,讓普通的變量也享受CAS操做帶來的線程安全性,這樣你能夠修改極少的代碼,來得到線程安全的保證。這聽起來是否是讓人很激動呢?數組

根據數據類型不一樣,這個Updater有3種,分別是AtomicIntegerFieldUpdater、AtomicLongFieldUpdater和AtomicReferenceFieldUpdater。顧名思義,它們分別能夠對int、long和普通對象就行CAS修改。安全

如今來思考這麼一個場景。假設某地要進行一次選舉。如今模擬這個機票場景,若是選民投了候選人一票,就記爲1,不然記爲0。最終的選票顯然就是全部數據的簡單求和。併發

01 public class AtomicIntegerFieldUpdaterDemo {
02     public static class Candidate{
03         int id;
04         volatile int score;
05     }
06     public final static AtomicIntegerFieldUpdater<Candidate> scoreUpdater 
07         = AtomicIntegerFieldUpdater.newUpdater(Candidate.class, "score");
08     //檢查Updater是否工做正確
09     public static AtomicInteger allScore=new AtomicInteger(0);
10     public static void main(String[] args) throws InterruptedException {
11         final Candidate stu=new Candidate();
12         Thread[] t=new Thread[10000];
13         for(int i = 0 ; i < 10000 ; i++) {  
14             t[i]=new Thread() {  
15                 public void run() {  
16                     if(Math.random()>0.4){
17                         scoreUpdater.incrementAndGet(stu);
18                         allScore.incrementAndGet();
19                     }
20                 }  
21             };
22             t[i].start();
23         }  
24         for(int i = 0 ; i < 10000 ; i++) {  t[i].join();}
25         System.out.println("score="+stu.score);
26         System.out.println("allScore="+allScore);
27     }
28 }

上述代碼模擬了這個計票場景。候選人的得票數量記錄在Candidate.score中。注意,它是一個普通的volatile變量。而volatile變量並非線程安全的。第6~7行定義了dom

AtomicIntegerFieldUpdater實例,用來對Candidate.score進行寫入。然後續的allScore咱們用來檢查AtomicIntegerFieldUpdater的正確性。若是AtomicIntegerFieldUpdater真的保證了線程安全,那麼最終Candidate.score和allScore的值必然是相等的。不然,就說明AtomicIntegerFieldUpdater根本沒有確保線程安全的寫入。第12~21行模擬了計票過程,這裏假設有大約60%的人投同意票,而且投票是隨機進行的。第17行使用Updater修改Candidate.score(這裏應該是線程安全的),第18行使用AtomicInteger計數,做爲參考基準。高併發

你們若是運行這段程序,不難發現,最終的Candidate.score老是和allScore絕對相等。這說明AtomicIntegerFieldUpdater很好地保證了Candidate.score的線程安全。工具


雖然AtomicIntegerFieldUpdater很好用,可是仍是有幾個注意事項:線程

第一,Updater只能修改它可見範圍內的變量。由於Updater使用反射獲得這個變量。若是變量不可見,就會出錯。好比若是score申明爲private,就是不可行的。設計

第二,爲了確保變量被正確的讀取,它必須是volatile類型的。若是咱們原有代碼中未申明這個類型,那麼簡單得申明一下就行,這不會引發什麼問題。指針

第三,因爲CAS操做會經過對象實例中的偏移量直接進行賦值,所以,它不支持static字段(Unsafe. objectFieldOffset()不支持靜態變量)。

好了,經過AtomicIntegerFieldUpdater,是否是讓咱們能夠更加爲所欲爲得對系統關鍵數據進行線程安全地保護呢?

【實戰Java高併發程序設計1】Java中的指針:Unsafe類
【實戰Java高併發程序設計2】無鎖的對象引用:AtomicReference
【實戰Java高併發程序設計 3】帶有時間戳的對象引用:AtomicStampedReference
【實戰Java高併發程序設計 4】數組也能無鎖AtomicIntegerArray

相關文章
相關標籤/搜索