7 安全發(fā)布對象

1??發(fā)布與逸出
① 概念

發(fā)布對象 : 使一個對象能夠被當前范圍之外的代碼所使用;
對象逸出 : 一種錯誤的發(fā)布,當一個對象還沒有構造完成時,就使他被其他線程所見;


② 代碼演示
/**
 * 發(fā)布對象
 */
@Slf4j
@NotThreadSafe
public class UnsafePublish {

    private String[] states = {"a", "b", "c"};

    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()));
    }
}

從結果可以看出這個類是不安全的,因為我們無法確認別的線程是否對這個對象進行修改;

/**
 * 對象逸出
 */
@Slf4j
@NotThreadSafe
@NotRecommend
public class Escape {

    private int thisCanBeEscape = 0;

    public Escape () {
        new InnerClass();
    }

    private class InnerClass {

        public InnerClass() {
            log.info("{}", Escape.this.thisCanBeEscape);
        }
    }

    public static void main(String[] args) {
        new Escape();
    }
}

這個也是一個線程不安全的,這會導致發(fā)布線程以外的其他線程會看到過期的值;


2??安全發(fā)布對象的四種方法

① 在靜態(tài)初始化函數(shù)中初始化一個對象引用;
② 將對象的引用保存到volatile類型域或者AtomicReference對象中;
③ 將對象的引用保存到某個正確構造對象的final類型域中;
④ 將對象的引用保存到一個由鎖保護的域中;


/**
 * 懶漢模式
 * 單例實例在第一次使用時進行創(chuàng)建
 */
@NotThreadSafe
public class SingletonExample1 {

    // 私有構造函數(shù)
    private SingletonExample1() { }

    // 單例對象
    private static SingletonExample1 instance = null;

    // 靜態(tài)的工廠方法
    public static SingletonExample1 getInstance() {
        if (instance == null) {
            instance = new SingletonExample1();
        }
        return instance;
    }
}

上邊的實現(xiàn)在單線程中沒有問題,因為我們在對象創(chuàng)建之前進行了判斷,如果在多線程環(huán)境下就會出現(xiàn)問題,這個問題是由于多個線程同時獲取到了不同的初始化對象導致;

/**
 * 餓漢模式
 * 單例實例在類裝載時進行創(chuàng)建
 */
@ThreadSafe
public class SingletonExample2 {

    // 私有構造函數(shù)
    private SingletonExample2() { }

    // 單例對象
    private static SingletonExample2 instance = new SingletonExample2();

    // 靜態(tài)的工廠方法
    public static SingletonExample2 getInstance() {
        return instance;
    }
}

這個類是線程安全的,因為我們使用了單例模式的餓漢式在類第一次被裝載的時候就會創(chuàng)建對象且因為是靜態(tài)的又只會被創(chuàng)建一次,所以他是線程安全的;但是這個也是有缺點的,如果初始化的時候執(zhí)行過多的操作會導致加載速度特別慢導致性能的問題,如果只進行資源的加載而沒有調(diào)用的話又會導致資源的浪費;

/**
 * 懶漢模式
 * 單例實例在第一次使用時進行創(chuàng)建
 */
@ThreadSafe
@NotRecommend
public class SingletonExample3 {

    // 私有構造函數(shù)
    private SingletonExample3() {

    }

    // 單例對象
    private static SingletonExample3 instance = null;

    // 靜態(tài)的工廠方法
    public static synchronized SingletonExample3 getInstance() {
        if (instance == null) {
            instance = new SingletonExample3();
        }
        return instance;
    }
}

獲取對象的方法經(jīng)過synchronized的修飾就會出現(xiàn)在同一個時間段內(nèi)只能有一個線程進行訪問,所以懶漢式也將會變成線程安全的;但是這樣的寫法我們并不推薦,因為加了synchronized雖然保證了線程安全但是卻帶來了性能上的開銷;

/**
 * 懶漢模式 -》 雙重同步鎖單例模式
 * 單例實例在第一次使用時進行創(chuàng)建
 */
@NotThreadSafe
public class SingletonExample4 {

    // 私有構造函數(shù)
    private SingletonExample4() { }

    // 單例對象
    private static SingletonExample4 instance = null;

    // 靜態(tài)的工廠方法
    public static SingletonExample4 getInstance() {
        if (instance == null) { // 雙重檢測機制        // B
            synchronized (SingletonExample4.class) { // 同步鎖
                if (instance == null) {
                    instance = new SingletonExample4(); // A - 3
                }
            }
        }
        return instance;
    }
}

但是這個類也不是線程安全的,當我們執(zhí)行到這一行代碼instance = new SingletonExample4();的時候;他會進行以下三步的操作:
1、memory = allocate() 分配對象的內(nèi)存空間
2、ctorInstance() 初始化對象
3、instance = memory 設置instance指向剛分配的內(nèi)存
在完成這三步以后我們的instance就只想實際分配的內(nèi)存地址了;在單線程的情況是沒有什么問題的但是在多線程情況下就會出現(xiàn)以下的情況:
JVM和cpu優(yōu)化,發(fā)生了指令重排
1、memory = allocate() 分配對象的內(nèi)存空間
3、instance = memory 設置instance指向剛分配的內(nèi)存
2、ctorInstance() 初始化對象
當發(fā)生了指令重排序以后,這個類就會變成線程不安全的了;

/**
 * 懶漢模式 -》 雙重同步鎖單例模式
 * 單例實例在第一次使用時進行創(chuàng)建
 */
@ThreadSafe
public class SingletonExample5 {

    // 私有構造函數(shù)
    private SingletonExample5() {

    }

    // 1、memory = allocate() 分配對象的內(nèi)存空間
    // 2、ctorInstance() 初始化對象
    // 3、instance = memory 設置instance指向剛分配的內(nèi)存

    // 單例對象 volatile + 雙重檢測機制 -> 禁止指令重排
    private volatile static SingletonExample5 instance = null;

    // 靜態(tài)的工廠方法
    public static SingletonExample5 getInstance() {
        if (instance == null) { // 雙重檢測機制        // B
            synchronized (SingletonExample5.class) { // 同步鎖
                if (instance == null) {
                    instance = new SingletonExample5(); // A - 3
                }
            }
        }
        return instance;
    }
}

因為前一個例子發(fā)生了指令重排序?qū)е铝司€程不安全,那么我們通過volatile關鍵字限制指令重排序這樣就會變成線程安全的了;這就是volatile的雙重檢測使用場景;關于懶漢模式我們就先分析到這里,接下來我們看一下惡漢模式


/**
 * 餓漢模式
 * 單例實例在類裝載時進行創(chuàng)建
 */
@ThreadSafe
public class SingletonExample6 {

    // 私有構造函數(shù)
    private SingletonExample6() { }

    // 單例對象
    private static SingletonExample6 instance = null;

    static {
        instance = new SingletonExample6();
    }

    // 靜態(tài)的工廠方法
    public static SingletonExample6 getInstance() {
        return instance;
    }

    public static void main(String[] args) {
        System.out.println(getInstance().hashCode());
        System.out.println(getInstance().hashCode());
    }
}

當我們在寫靜態(tài)域或者靜態(tài)代碼塊的時候一定要注意書寫順序否則會出現(xiàn)NPE;

/**
 * 枚舉模式:最安全
 */
@ThreadSafe
@Recommend
public class SingletonExample7 {

    // 私有構造函數(shù)
    private SingletonExample7() { }

    public static SingletonExample7 getInstance() {
        return Singleton.INSTANCE.getInstance();
    }

    private enum Singleton {
        INSTANCE;

        private SingletonExample7 singleton;

        // JVM保證這個方法絕對只調(diào)用一次
        Singleton() {
            singleton = new SingletonExample7();
        }

        public SingletonExample7 getInstance() {
            return singleton;
        }
    }
}

當我們通過枚舉來初始化這個對象的時候,它可以保證這個方法絕對只會被執(zhí)行一次且是在這個類調(diào)用之前初始化的,因此這個類是線程絕對安全的,推薦使用這種方式,因為這種方式比懶漢式更安全,比餓漢式更加節(jié)省資源;

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 從三月份找實習到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時芥藍閱讀 42,801評論 11 349
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結構(3).初始化時...
    歐辰_OSR閱讀 30,242評論 8 265
  • 本文是我自己在秋招復習時的讀書筆記,整理的知識點,也是為了防止忘記,尊重勞動成果,轉(zhuǎn)載注明出處哦!如果你也喜歡,那...
    波波波先森閱讀 11,614評論 4 56
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,658評論 1 32
  • 不知道你有沒有遇到過,做過的夢居然在現(xiàn)實中發(fā)生了。這不,LOL知名主播小智就遇到了這樣的事情。 據(jù)小智透露,今日凌...
    f伐木累閱讀 2,374評論 0 0

友情鏈接更多精彩內(nèi)容