java並發編程實戰:第三章----對象的共享
我們不僅僅希望防止某個線程使用某個狀態時,另一個線程在修改它;我們還希望某個線程修改了某個狀態後,其他線程能夠看到狀態的變化。
一、可見性
重排序:在沒有同步的情況下,編譯器、處理器可能對代碼的執行順序進行一些調整
例如如下代碼,由於沒有使用同步機制,讀線程可能看不見ready的修改,而一直循環下去;也可能由於重排序,看到了ready的修改number仍沒修改而輸出0
1、失效數據
在缺少同步的程序中產生錯誤的結果的一種情況。造成程序的不確定性。
2、非原子的64位操作
即使是失效數據也是程序過去運行中產生的數據。
但執行非原子的64位操作,JVM會分解為兩個32位操作,從而可能造成錯誤的值。使用volatile關鍵字解決
3、加鎖與可見性
加鎖的作用不僅僅局限在互斥,還包括內存可見性。
鎖可以確保某個線程以一種可以預測的方式來查看另一個線程的執行結果。即前一線程結果後一線程可見。
4、volatile變量
比synchronized更輕量級的同步機制,編譯器和運行時都會註意到這個變量是共享的,在該變量上的操作和其他內存操作不會放在一起做重排序。
讀取volatile變量的開銷很小。
在開發階段最好時候server模式,因為server模式會做更多的優化,例如重排序等可能導致並發問題
加鎖機制能保證可見性和原子性,volatile只能保證可見性,使用條件:
- 寫入不依賴當前值
- 訪問時不用加鎖
二、發布與逸出
發布:使對象能夠在當前作用於外的代碼使用,例子:
逸出:不該發布的對象被發布
不要在構造方法中使this的引用溢出,會發布一個不完整的對象
三、線程封閉
當訪問共享的可變數據時,通常需要同步。一種避免使用同步的方式就是不共享數據。如果僅在單線程內訪問數據,就不需要同步。這種技術稱為線程封閉
需確保封閉在線程中的對象不會溢出
1、Ad-hoc線程封閉:完全有程序實現線程封閉,少用
2、棧封閉:只能通過局部變量訪問對象,因為其他線程無法訪問執行線程的棧區域
3、TheadLocal類ThreadLocal<T> -> Map<Thread, T>
ThreadLocal類用來提供線程內部的局部變量。這種變量在多線程環境下訪問(通過get或set方法訪問)時能保證各個線程裏的變量相對獨立於其他線程內的變量。
詳見:(轉)http://qifuguang.me/2015/09/02/[Java%E5%B9%B6%E5%8F%91%E5%8C%85%E5%AD%A6%E4%B9%A0%E4%B8%83]%E8%A7%A3%E5%AF%86ThreadLocal/
四、不變性
滿足同步需求的另外一種方法是使用不可變對象。不可變對象一定是線程安全的
不可變對象:對象創建出來後無論如何對此對象進行非暴力操作(不用反射),此對象的狀態(實例域的值)都不會發生變化,那麽此對象就是不可變的,相應類就是不可變類,跟是否用 final 修飾沒關系。(例:final域中保存了可變對象的引用)
條件:對象創建後狀態不更改;域均用final修飾;正確創建(構造函數沒有this溢出)
1、final域
2、通過volatile發布不可修改對象實現線程安全
因為cache是不可修改的,所以可以保證每個i和factors是對應的,並用volatile保證了可見性
五、安全發布
1、不正確的發布,正確對象被破壞
未被創建完成的正確對象被發布,其他線程可能看到一個失效的值或空指針
2、不可變對象與初始化安全性
任何線程都可以在不需要同步的情況下訪問不可變對象,即使發布不可變對象時沒有同步
3、安全發布的常用模式
可變對象通過安全的方式發布,即發布和使用他的線程必須進行同步
發布靜態對象時,使用靜態初始化器,由JVM在累得初始化時執行,並且存在同步機制
將對象的引用保存到volatile類型或AtomicReferance對象中或正確構造的final中
將對象發布到有鎖保護的域中(HashTable,synchronizedMap,ConcurrentMap,Vector,CopyOnWriteArrayList,CopyOnWriteArraySet,synchronizedList,synchronizedSet,BlockingQueue,ConcurrentLinkedQueue)
4、事實不可變對象:技術上可變,實際不會變
任何線程都可以在不需要同步的情況下訪問安全發布的事實不可變對象
5、可變對象
需要安全發布,並且訪問需要同步機制
6、安全的共享對象
java並發編程實戰:第三章----對象的共享