有時候,由於初期考慮不周,或者後期的需求變化,一些普通變數可能也會有線程安全的需求。
【實戰Java高併發程式設計 1】Java中的指針:Unsafe類
【實戰Java高併發程式設計 2】無鎖的對象引用:AtomicReference
【實戰Java高併發程式設計 3】帶有時間戳的對象引用:AtomicStampedReference
【實戰Java高併發程式設計 4】數組也能無鎖:AtomicIntegerArray
有時候,由於初期考慮不周,或者後期的需求變化,一些普通變數可能也會有線程安全的需求。如果改動不大,我們可以簡單地修改程式中每一個使用或者讀取這個變數的地方。但顯然,這樣並不符合軟體設計中的一條重要原則——開閉原則。也就是系統對功能的增加應該是開發的,而對修改應該是相對保守的。而且,如果系統里使用到這個變數的地方特別多,一個一個修改也是一件令人厭煩的事情(況且很多使用場景下可能只是只讀的,並無線程安全的強烈要求,完全可以保持原樣)。
如果你有這種困擾,在這裡根本不需要擔心,因為在原子包里還有一個實用的工具類AtomicIntegerFieldUpdater。它可以讓你在不改動(或者極少改動)原有代碼的基礎上,讓普通的變數也享受CAS操作帶來的線程安全性,這樣你可以修改極少的代碼,來獲得線程安全的保證。這聽起來是不是讓人很激動呢?
根據數據類型不同,這個Updater有3種,分別是AtomicIntegerFieldUpdater、AtomicLongFieldUpdater和AtomicReferenceFieldUpdater。顧名思義,它們分別可以對int、long和普通對象就行CAS修改。
現在來思考這麼一個場景。假設某地要進行一次選舉。現在模擬這個機票場景,如果選民投了候選人一票,就記為1,否則記為0。最終的選票顯然就是所有數據的簡單求和。
public class AtomicIntegerFieldUpdaterDemo { public static class Candidate{ int id; volatile int score; } public final static AtomicIntegerFieldUpdater<Candidate> scoreUpdater = AtomicIntegerFieldUpdater.newUpdater(Candidate.class, "score"); //檢查Updater是否工作正確 public static AtomicInteger allScore=new AtomicInteger(0); public static void main(String[] args) throws InterruptedException { final Candidate stu=new Candidate(); Thread[] t=new Thread[10000]; for(int i = 0 ; i < 10000 ; i++) { t[i]=new Thread() { public void run() { if(Math.random()>0.4){ scoreUpdater.incrementAndGet(stu); allScore.incrementAndGet(); } } }; t[i].start(); } for(int i = 0 ; i < 10000 ; i++) { t[i].join();} System.out.println("score="+stu.score); System.out.println("allScore="+allScore); } }
上述代碼模擬了這個計票場景。候選人的得票數量記錄在Candidate.score中。註意,它是一個普通的volatile變數。而volatile變數並不是線程安全的。第6~7行定義了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高併發程式設計》