原文:慕課網高併發實戰(五)- 安全發佈對象html
發佈對象:使一個對象可以被當前範圍以外的代碼所使用
對象溢出:一種錯誤的發佈,當一個對象尚未構造完成時,就使它被其餘線程所見java
不正確的發佈可變對象致使的兩種錯誤:安全
一、發佈線程意外的全部線程均可以看到被髮布對象的過時的值
二、線程看到的被髮布對象的引用是最新的,然而被髮布對象的狀態倒是過時的併發
下面使用代碼對不安全的發佈和對象溢出進行說明:
不安全的發佈示例函數
import com.gwf.concurrency.annoations.NotThreadSafe; import lombok.extern.slf4j.Slf4j; import java.util.Arrays; /** * 不安全的發佈 * @author gaowenfeng * @date 2018-03-19 */ @Slf4j @NotThreadSafe public class UnsafePublish { private String[] states = {"a","b","c"}; /** * 經過public發佈級別發佈了類的域,在類的外部,任何線程均可以訪問這個域 * 這樣是不安全的,由於咱們沒法檢查其餘線程是否會修改這個域致使了錯誤 * @return */ public String[] getStates() { return states; } public static void main(String[] args) { UnsafePublish unsafePublish = new UnsafePublish(); log.info("{}", Arrays.toString(unsafePublish.getStates())); unsafePublish.getStates()[0] = "d"; log.info("{}", Arrays.toString(unsafePublish.getStates())); } }
對象溢出示例高併發
/** * 對象溢出 * 在對象構造完成以前,不能夠將其發佈 * @author gaowenfeng * @date */ @Slf4j @NotThreadSafe @NotRecommend public class Escape { private int thisCannBeEscape = 0; public Escape(){ new InnerClass(); } /** * 包含了對封裝實例的隱藏和引用,這樣在對象沒有被正確構造完成以前就會被髮布,由此致使不安全的因素在裏面 * 致使this引用在構造期間溢出的錯誤,他是在構造函數構造過程當中啓動了一個線程,形成this引用的溢出 * 新線程只是在對象構造完畢以前就已經看到他了,因此若是要在構造函數中建立線程,那麼不要啓動它, * 而是應該才用一個專有的start,或是其餘的方式統一啓動線程 * 使用工廠方法和私有構造函數來完成對象建立和監聽器的註冊來避免不正確的發佈 */ private class InnerClass{ public InnerClass(){ log.info("{}",Escape.this.thisCannBeEscape); } } public static void main(String[] args) { new Escape(); } }
安全發佈對象有如下四種方法:
一、在靜態初始化函數中初始化一個對象引用
二、將對象的引用保存到volatile類型域或者AtomicReference對象中
三、將對象的引用保存到某個正確構造的final類型域中
四、將對象的引用保存到一個由鎖保護的域中this
1和2實例參考雙重檢測機制的線程安全的單例模式、4的實例能夠參考線程安全的懶漢式單例模式。線程安全的單例模式的幾種實現方式參考: 單例模式線程