Java單例模式實現(xiàn)被忽視了的問題

單例模式應該是設計模式中應用最廣泛的模式旋奢,通過單例模式迄薄,保證在系統(tǒng)的生命周期中只有一個在運行恳邀,在java中懦冰,單面經常寫成如下格式。

public class Singleton {  
   
    private Singleton() {}  
  
     private static Singleton instance;  
  
     public static Singleton getInstance() {  
    
        if(instance == null){
            instance = new Singleton();
         }
        return instance;  
      }  
}  

看起來很漂亮的代碼谣沸,但是如果有在多線程的環(huán)境下呢?線程1執(zhí)行完了if判斷后被中斷笋颤, 線程2開始執(zhí)行并生成了一個實例乳附,線程1接著執(zhí)行,又生成了一個伴澄。最簡單的方法是加鎖赋除。代碼如下:

public class Singleton {  
   
    private Singleton() {}  
  
     private static Singleton instance;  
  
     public static synchronized Singleton getInstance() {  
    
        if(instance == null){
            instance = new Singleton();
         }
        return instance;  
      }  
}  

這樣寫,這個代碼執(zhí)行沒有問題非凌,但是想一想举农,單例模式只生成一個實例,所以實際上只有在第一次的時候if語句條件成立敞嗡,在后面無論執(zhí)行多少次颁糟,都會直接返回靜態(tài)實例,但是每次都要鎖整段代碼喉悴,性能和效率上是一種浪費棱貌。那么怎么既能保證安全,又能保證性能呢箕肃,有好幾種方法婚脱,這里先說比較常用的雙檢測(double-check)機制。

public class Singleton {  
   
    private Singleton() {}  
  
     private static Singleton instance;  
  
     public static Singleton getInstance() {  
    
        If (instance == null) {

            synchronized(Singleton.class){
                 if(instance == null){
                    instance = new Singleton();
                 }
             }
         }
        return instance;  
      }  
}  

這樣在第一次if條件成立的時候勺像,即使線程被中斷障贸,因為有鎖保護,在生成實例的過程中被保護吟宦,被中斷的線程或者新的線程在這個過程中不能執(zhí)行篮洁,而等鎖推出后,其他線程恢復執(zhí)行的時候督函,又要做一次判斷嘀粱,所以不會出現(xiàn)重復生成激挪。而實例生成后,每次只需要執(zhí)行第一個if判斷锋叨,就直接返回垄分,所以也不影響代碼效率⊥藁牵看起來很完美薄湿,可以還沒完,這個代碼還有一個bug偷卧,就是java的內存模型的問題被疏忽了【第一個問題豺瘤,java的內存模型】。


圖片發(fā)自簡書App

在java多線程的時候听诸,一般每個線程都會有一個工作內存(對CPU的高速緩存機制的抽象)坐求,如上圖所示的工作內存和主內存之間的關系及操作順序。線程運行的時候是使用和修改自己的工作內存中的變量晌梨,而并不會立即回寫到主內存桥嗤,所以如果上面的代碼,如果線程1執(zhí)行過程中被打斷仔蝌,而線程2執(zhí)行完成泛领,并生成一個實例,但是由于線程1還是使用的是自己工作內存的值敛惊,那么還會出現(xiàn)多次生成實例的問題渊鞋。所以我們只考慮了原子性,而疏忽了多線程變成的可見性和有序性瞧挤。所以可以修改一行代碼:

private static volatile Singleton instance;

這個差別就是用volatile修飾instance變量锡宋,volatile聲明的變量解決可見性和有序性,對于可見行:線程中每次use變量時皿伺,都需要連續(xù)執(zhí)行read->load->use幾項操作员辩,即所謂的每次使用都要從主內存更新變量值,這樣其它線程的修改對該線程就是可見的鸵鸥。并且奠滑,線程每次assign變量時,都需要連續(xù)執(zhí)行assign->store->write幾項操作妒穴,即所謂每次更新完后都會回寫到主內存宋税,這樣使得其它線程讀到的都是最新數據。對于有序性讼油,對于volatile變量前面的代碼的修改不會被優(yōu)化到volatile變量后面杰赛,來避免虛擬機的優(yōu)化造成代碼的執(zhí)行順序的變化。

// Thread 1

Worker worker = new Worker;
Boolean initialed = true;

//Thread 2

While(! Initialed){
    sleep(100);
}
worker.dosomething();

如果不用volatile生命initialed變量矮台,假如虛擬機對線程1的代碼做了重排乏屯,線程2執(zhí)行過程中就會出異常(雖然概率可能會非常低)根时。

public class Test {
    public volatile int inc = 0;
     
    public void increase() {
        inc++;
    }
     
    public static void main(String[] args) {
        final Test test = new Test();
        for(int i=0;i<10;i++){
            new Thread(){
                public void run() {
                    for(int j=0;j<1000;j++)
                        test.increase();
                };
            }.start();
        }
         
        while(Thread.activeCount()>1)  //保證前面的線程都執(zhí)行完
            Thread.yield();
        System.out.println(test.inc);
    }
}

最后再看后面這段代碼,運行結果是10000嗎辰晕?實際上這段程序并不能保證結果是10000蛤迎,因為volatile只能保證變量的可見性,而不能保證變量的原子性含友,而在java中替裆,只有賦值和基本類型變量的讀取能保證原子性。假如線程1剛從主內存read了變量后被中斷窘问,另外一個線程開始執(zhí)行辆童,讀取了主內存的變量,做了自加操作惠赫,等線程1開始執(zhí)行把鉴,還是用剛才讀取的主內存的值,因為可見性只是保證每次從主內存去讀值儿咱。

編碼是個苦力纸镊,每一行代碼都得仔細思考,怎么讓coder有動力去思考概疆,有時間去思考?

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末峰搪,一起剝皮案震驚了整個濱河市岔冀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌概耻,老刑警劉巖使套,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異鞠柄,居然都是意外死亡侦高,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門厌杜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來奉呛,“玉大人,你說我怎么就攤上這事夯尽∏谱常” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵匙握,是天一觀的道長咆槽。 經常有香客問我,道長圈纺,這世上最難降的妖魔是什么秦忿? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任麦射,我火速辦了婚禮,結果婚禮上灯谣,老公的妹妹穿的比我還像新娘潜秋。我一直安慰自己,他們只是感情好酬屉,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布半等。 她就那樣靜靜地躺著,像睡著了一般呐萨。 火紅的嫁衣襯著肌膚如雪杀饵。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天谬擦,我揣著相機與錄音切距,去河邊找鬼。 笑死惨远,一個胖子當著我的面吹牛谜悟,可吹牛的內容都是我干的。 我是一名探鬼主播北秽,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼葡幸,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了贺氓?” 一聲冷哼從身側響起蔚叨,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎辙培,沒想到半個月后蔑水,有當地人在樹林里發(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡扬蕊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年搀别,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片尾抑。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡歇父,死狀恐怖,靈堂內的尸體忽然破棺而出蛮穿,到底是詐尸還是另有隱情庶骄,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布践磅,位于F島的核電站单刁,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜羔飞,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一肺樟、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧逻淌,春花似錦么伯、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至骨望,卻和暖如春硬爆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背擎鸠。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工缀磕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人劣光。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓袜蚕,卻偏偏與公主長得像,于是被迫代替她去往敵國和親绢涡。 傳聞我的和親對象是個殘疾皇子牲剃,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內容

  • 從三月份找實習到現(xiàn)在,面了一些公司雄可,掛了不少颠黎,但最終還是拿到小米、百度滞项、阿里、京東夭坪、新浪文判、CVTE、樂視家的研發(fā)崗...
    時芥藍閱讀 42,243評論 11 349
  • 第6章類文件結構 6.1 概述 6.2 無關性基石 6.3 Class類文件的結構 java虛擬機不和包括java...
    kennethan閱讀 931評論 0 2
  • 小伙伴兒們早上好室梅!今天是周一戏仓,美好的一周又開始了。都說周一是“忙”day亡鼠,準備好開啟忙碌的一周了嗎赏殃?【今日新聞】-...
    砧弟閱讀 171評論 0 0
  • 俗話說的好不想寫出的爆文的寫手不是好農民!(一句被玩壞了的名言哈)所以嘛间涵!我也是想寫出爆文的仁热,想要寫出爆文必須先看...
    1281d95c0dac閱讀 223評論 5 5
  • 改作業(yè)。 有一條是回家給父母講在學校的開心事或難忘事勾哩。一為促進親子交流抗蠢,增進雙方了解举哟,二來訓練口語交際,語言表達能...
    葉子飄飄吧閱讀 558評論 0 1