摘要:有時候,由于初期考慮不周,或者后期的需求變化,一些普通變量可能也會有線程安全的需求。它可以讓你在不改動或者極少改動原有代碼的基礎上,讓普通的變量也享受操作帶來的線程安全性,這樣你可以修改極少的代碼,來獲得線程安全的保證。
有時候,由于初期考慮不周,或者后期的需求變化,一些普通變量可能也會有線程安全的需求。如果改動不大,我們可以簡單地修改程序中每一個使用或者讀取這個變量的地方。但顯然,這樣并不符合軟件設計中的一條重要原則——開閉原則。也就是系統對功能的增加應該是開發的,而對修改應該是相對保守的。而且,如果系統里使用到這個變量的地方特別多,一個一個修改也是一件令人厭煩的事情(況且很多使用場景下可能只是只讀的,并無線程安全的強烈要求,完全可以保持原樣)。
如果你有這種困擾,在這里根本不需要擔心,因為在原子包里還有一個實用的工具類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 AtomicIntegerFieldUpdaterscoreUpdater 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行定義了
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
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/65622.html
摘要:在本例中,講述的無鎖來自于并發包我們將這個無鎖的稱為。在這里,我們使用二維數組來表示的內部存儲,如下變量存放所有的內部元素。為什么使用二維數組去實現一個一維的呢這是為了將來進行動態擴展時可以更加方便。 我們已經比較完整得介紹了有關無鎖的概念和使用方法。相對于有鎖的方法,使用無鎖的方式編程更加考驗一個程序員的耐心和智力。但是,無鎖帶來的好處也是顯而易見的,第一,在高并發的情況下,它比有鎖...
摘要:參考何去何從的并行計算忘記該死的并行并行程序的復雜性和亂序性,并行程序設計十分復雜。可怕的現實摩爾定律的失效單核上的晶體管數目達到極限。并發級別阻塞重入鎖無饑餓兩個線程優先級不同,低優先級的可能產生饑餓。 Chapter1 參考:https://github.com/chengbingh... 1.1何去何從的并行計算 1.1.1 忘記該死的并行并行程序的復雜性和亂序性,并行程序設計十...
摘要:通過指令重排可以減少流水線停頓提升巨大效率原則程序順序原則一個線程內保證語義的串行性。鎖規則解鎖必然發生在隨后的加鎖前。線程的方法先于它的每一個動作。 1. 基本概念 同步(Synchronous)和異步(Asynchronous) 并發(Conncurrency)和并行(Parallelism) 臨界區 阻塞(Blocking)與非阻塞(Non-Blocking) 死鎖(Deadl...
摘要:當前可用的原子數組有和,分別表示整數數組型數組和普通的對象數組。摘自實戰高并發程序設計一書實戰高并發程序設計中的指針類實戰高并發程序設計無鎖的對象引用實戰高并發程序設計帶有時間戳的對象引用 除了提供基本數據類型外,JDK還為我們準備了數組等復合結構。當前可用的原子數組有:AtomicIntegerArray、AtomicLongArray和AtomicReferenceArray,分別...
閱讀 2943·2023-04-26 01:49
閱讀 2074·2021-10-13 09:39
閱讀 2287·2021-10-11 11:09
閱讀 928·2019-08-30 15:53
閱讀 2821·2019-08-30 15:44
閱讀 927·2019-08-30 11:12
閱讀 2980·2019-08-29 17:17
閱讀 2378·2019-08-29 16:57