7 安全發(fā)布對象

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

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


② 代碼演示
/**
 * 發(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()));
    }
}

從結(jié)果可以看出這個類是不安全的,因?yàn)槲覀儫o法確認(rèn)別的線程是否對這個對象進(jìn)行修改;

/**
 * 對象逸出
 */
@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();
    }
}

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


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

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


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

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

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

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

上邊的實(shí)現(xiàn)在單線程中沒有問題,因?yàn)槲覀冊趯ο髣?chuàng)建之前進(jìn)行了判斷,如果在多線程環(huán)境下就會出現(xiàn)問題,這個問題是由于多個線程同時獲取到了不同的初始化對象導(dǎo)致;

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

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

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

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

這個類是線程安全的,因?yàn)槲覀兪褂昧藛卫J降酿I漢式在類第一次被裝載的時候就會創(chuàng)建對象且因?yàn)槭庆o態(tài)的又只會被創(chuàng)建一次,所以他是線程安全的;但是這個也是有缺點(diǎn)的,如果初始化的時候執(zhí)行過多的操作會導(dǎo)致加載速度特別慢導(dǎo)致性能的問題,如果只進(jìn)行資源的加載而沒有調(diào)用的話又會導(dǎo)致資源的浪費(fèi);

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

    // 私有構(gòu)造函數(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)只能有一個線程進(jìn)行訪問,所以懶漢式也將會變成線程安全的;但是這樣的寫法我們并不推薦,因?yàn)榧恿藄ynchronized雖然保證了線程安全但是卻帶來了性能上的開銷;

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

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

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

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

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

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

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

    }

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

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

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

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


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

    // 私有構(gòu)造函數(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());
    }
}

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

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

    // 私有構(gòu)造函數(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;
        }
    }
}

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末葱弟,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子猜丹,更是在濱河造成了極大的恐慌芝加,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件射窒,死亡現(xiàn)場離奇詭異藏杖,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)脉顿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進(jìn)店門蝌麸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人艾疟,你說我怎么就攤上這事来吩。” “怎么了蔽莱?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵弟疆,是天一觀的道長。 經(jīng)常有香客問我碾褂,道長兽间,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任正塌,我火速辦了婚禮嘀略,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘乓诽。我一直安慰自己帜羊,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布鸠天。 她就那樣靜靜地躺著讼育,像睡著了一般。 火紅的嫁衣襯著肌膚如雪稠集。 梳的紋絲不亂的頭發(fā)上奶段,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天,我揣著相機(jī)與錄音剥纷,去河邊找鬼痹籍。 笑死,一個胖子當(dāng)著我的面吹牛晦鞋,可吹牛的內(nèi)容都是我干的蹲缠。 我是一名探鬼主播棺克,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼线定!你這毒婦竟也來了娜谊?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤斤讥,失蹤者是張志新(化名)和其女友劉穎纱皆,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體芭商,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡抹剩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蓉坎。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡胡嘿,死狀恐怖蛉艾,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情衷敌,我是刑警寧澤勿侯,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站缴罗,受9級特大地震影響助琐,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜面氓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一兵钮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧舌界,春花似錦掘譬、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至藐握,卻和暖如春靴拱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背猾普。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工袜炕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人抬闷。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓妇蛀,卻偏偏與公主長得像耕突,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子评架,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評論 2 348

推薦閱讀更多精彩內(nèi)容