(二)Java并發(fā)學(xué)習(xí)筆記--安全發(fā)布對(duì)象

逸出的方式

上邊關(guān)于逸出的概念講述的很是模糊值朋,下面列舉幾個(gè)逸出的示例。

  1. 通過(guò)靜態(tài)變量引用逸出
public static Set<Secret> knownSecrets;
public void initialize() {
    knowsSecrets = new HashSet<Secret>();
}

上邊代碼示例中巩搏,調(diào)用initialize方法昨登,發(fā)布了knowSecrets對(duì)象。當(dāng)你向knowSecrets中添加一個(gè)Secret時(shí)贯底,會(huì)同時(shí)將Secret對(duì)象發(fā)布出去篙骡,原因是可以通過(guò)遍歷knowSecrets獲取到Secret對(duì)象的引用,然后進(jìn)行修改丈甸。

  1. 通過(guò)非靜態(tài)(私有)方法
class UnsafeStates {
    private String[] states = new String[]{"AK", "AL"};
    public String[] getStates() {
        return states;
    }
}

以這種方式發(fā)布的states會(huì)出問(wèn)題糯俗,任何一個(gè)調(diào)用者都能修改它的內(nèi)容。數(shù)組states已經(jīng)逸出了它所屬的范圍睦擂,這個(gè)本應(yīng)該私有的數(shù)據(jù)得湘,事實(shí)上已經(jīng)變成共有的了。

  1. this逸出
public class ThisEscape {
  public ThisEscape(EventSource source) {
        source.registerListener(new EventListener() {
              public void onEvent(Event e) {
                    doSomething(e);
              }
        });
  }
}

在上邊代碼中顿仇,當(dāng)我們實(shí)例化ThisEscape對(duì)象時(shí)淘正,會(huì)調(diào)用source的registerListener方法時(shí),便啟動(dòng)了一個(gè)線程臼闻,而且這個(gè)線程持有了ThisEscape對(duì)象(調(diào)用了對(duì)象的doSomething方法)鸿吆,但此時(shí)ThisEscape對(duì)象卻沒(méi)有實(shí)例化完成(還沒(méi)有返回一個(gè)引用),所以我們說(shuō)述呐,此時(shí)造成了一個(gè)this引用逸出惩淳,即還沒(méi)有完成的實(shí)例化ThisEscape對(duì)象的動(dòng)作,卻已經(jīng)暴露了對(duì)象的引用乓搬,使其他線程可以訪問(wèn)還沒(méi)有構(gòu)造好的對(duì)象思犁,可能會(huì)造成意料不到的問(wèn)題。

通過(guò)上述示例进肯,個(gè)人理解激蹲,對(duì)逸出的概念應(yīng)該定義為:

一個(gè)對(duì)象,超出了它原本的作用域江掩,而可以被其它對(duì)象進(jìn)行修改学辱,而這種修改及修改的結(jié)果是無(wú)法預(yù)測(cè)的。換句話說(shuō):一個(gè)對(duì)象發(fā)布后环形,它的狀態(tài)應(yīng)該是穩(wěn)定的策泣,修改是可被檢測(cè)到的。如果在其它線程修改(或做其它操作)一個(gè)對(duì)象后導(dǎo)致對(duì)象的狀態(tài)未知斟赚,就可以說(shuō)這個(gè)對(duì)象逸出了着降。

總之差油,一個(gè)對(duì)象逸出后拗军,不論其它線程或?qū)ο笫欠袷褂眠@個(gè)逸出的對(duì)象都不重要任洞,重要的是,被誤用及被誤用后的未知結(jié)果的風(fēng)險(xiǎn)總是存在的发侵。

PS 書(shū)中給出了避免this逸出的方法:

public class SafeListener {
  private final EventListener listener;

  private SafeListener() {
    listener = new EventListener() {
          public void onEvent(Event e) {
                doSomething(e);
          }
    };
     }

  public static SafeListener newInstance(EventSource source) {
    SafeListener safe = new SafeListener();
    source.registerListener(safe.listener);
    return safe;
  }
}

在這個(gè)構(gòu)造中交掏,我們看到的最大的一個(gè)區(qū)別就是:當(dāng)構(gòu)造好了SafeListener對(duì)象之后,我們才啟動(dòng)了監(jiān)聽(tīng)線程刃鳄,也就確保了SafeListener對(duì)象是構(gòu)造完成之后在使用的SafeListener對(duì)象盅弛。

對(duì)于這樣的技術(shù),書(shū)里面也有這樣的注釋?zhuān)?br> 具體來(lái)說(shuō)叔锐,只有當(dāng)構(gòu)造函數(shù)返回時(shí)挪鹏,this引用才應(yīng)該從線程中逸出。構(gòu)造函數(shù)可以將this引用保存到某個(gè)地方愉烙,只要其他線程不會(huì)在構(gòu)造函數(shù)完成之前使用它讨盒。

安全發(fā)布對(duì)象

  • 在靜態(tài)初始化函數(shù)中初始化一個(gè)對(duì)象引用
  • 將對(duì)象的應(yīng)用保存到volatile類(lèi)型的域或者AtomicReferance對(duì)象中
  • 將對(duì)象的引用保存到某個(gè)正確構(gòu)造對(duì)象的final類(lèi)型域中
  • 將對(duì)象的引用保存到一個(gè)由鎖保護(hù)的域中。
/**
 * 懶漢模式(線程不安全)
 * 單例實(shí)例在第一次使用時(shí)進(jìn)行創(chuàng)建
 */
@NotThreadSafe
public class SingletonExample1 {

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

    }

    // 單例對(duì)象
    private static SingletonExample1 instance = null;

    // 靜態(tài)的工廠方法
    public static SingletonExample1 getInstance() {
        // 這里同時(shí)有兩個(gè)線程進(jìn)入就可能同時(shí)初始化兩個(gè)對(duì)象
        if (instance == null) {
            instance = new SingletonExample1();
        }
        return instance;
    }
}

懶漢模式本身是線程不安全的步责,如果想要實(shí)現(xiàn)線程安全可以通過(guò)synchronized關(guān)鍵字實(shí)現(xiàn):

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

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

    }

    // 單例對(duì)象
    private static SingletonExample3 instance = null;

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

但此中方式不推薦使用返顺,應(yīng)該它通過(guò)同一時(shí)間內(nèi)只允許一個(gè)線程來(lái)訪問(wèn)的方式實(shí)現(xiàn)線程安全,但是卻帶來(lái)了性能上面的開(kāi)銷(xiāo)蔓肯。

我們可以通過(guò)以下方式來(lái)實(shí)現(xiàn)線程安全:

懶漢模式 -》 volatile + 雙重同步鎖單例模式


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

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

    }

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

    // JVM和cpu優(yōu)化秉扑,發(fā)生了指令重排(多線程 )

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

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

    public static SingletonExample4 getInstance() {
        if (instance == null) { // 雙重檢測(cè)機(jī)制        // B
            synchronized (SingletonExample4.class) { // 同步鎖
                if (instance == null) {
                    instance = new SingletonExample4(); // A - 3
                }
            }
        }
        return instance;
    }
}
/**
 * 餓漢模式
 * 單例實(shí)例在類(lèi)裝載時(shí)進(jìn)行創(chuàng)建
 */
@ThreadSafe
public class SingletonExample2 {

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

    }

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

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

餓漢模式不會(huì)有線程問(wèn)題,但是在類(lèi)加載時(shí)實(shí)例化對(duì)象旧噪。使用時(shí)要考慮兩點(diǎn):

  1. 私有構(gòu)造函數(shù)在使用時(shí)沒(méi)有過(guò)多的邏輯處理(銷(xiāo)毀性能吨娜,慢)
  2. 這個(gè)對(duì)象一定會(huì)被使用(浪費(fèi)資源)

在靜態(tài)代碼塊中實(shí)例化一個(gè)對(duì)象:

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

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

    }

    // 單例對(duì)象
    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());
    }
}

枚舉模式:

/**
 * 枚舉模式:最安全
 */
@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保證這個(gè)方法絕對(duì)只調(diào)用一次
        Singleton() {
            singleton = new SingletonExample7();
        }

        public SingletonExample7 getInstance() {
            return singleton;
        }
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市淘钟,隨后出現(xiàn)的幾起案子宦赠,更是在濱河造成了極大的恐慌,老刑警劉巖米母,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件勾扭,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡铁瞒,警方通過(guò)查閱死者的電腦和手機(jī)妙色,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)慧耍,“玉大人身辨,你說(shuō)我怎么就攤上這事丐谋。” “怎么了煌珊?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵号俐,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我定庵,道長(zhǎng)吏饿,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任蔬浙,我火速辦了婚禮猪落,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘畴博。我一直安慰自己许布,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布绎晃。 她就那樣靜靜地躺著蜜唾,像睡著了一般。 火紅的嫁衣襯著肌膚如雪庶艾。 梳的紋絲不亂的頭發(fā)上袁余,一...
    開(kāi)封第一講書(shū)人閱讀 49,784評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音咱揍,去河邊找鬼颖榜。 笑死,一個(gè)胖子當(dāng)著我的面吹牛煤裙,可吹牛的內(nèi)容都是我干的掩完。 我是一名探鬼主播,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼硼砰,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼且蓬!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起题翰,我...
    開(kāi)封第一講書(shū)人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤恶阴,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后豹障,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體冯事,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年血公,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了昵仅。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡累魔,死狀恐怖摔笤,靈堂內(nèi)的尸體忽然破棺而出够滑,到底是詐尸還是另有隱情,我是刑警寧澤籍茧,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布版述,位于F島的核電站梯澜,受9級(jí)特大地震影響寞冯,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜晚伙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一吮龄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧咆疗,春花似錦漓帚、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至迅皇,卻和暖如春昧辽,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背登颓。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工搅荞, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人框咙。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓咕痛,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親喇嘱。 傳聞我的和親對(duì)象是個(gè)殘疾皇子茉贡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

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