1. 程式人生 > >調侃《Head First 設計模式》之單例模式

調侃《Head First 設計模式》之單例模式

           對於一個類來說,平常我們可以隨便new出無限多個物件(只要記憶體hold得住),但是像執行緒池、快取、對話方塊、日誌物件、裝置驅動程式的物件只能有一個物件,如果製造多個例項就會出現問題。比如程式行為異常,資源使用過量等。

       那如何讓一個類只能有一個物件呢?也許你想到了在另一個類持有該類的物件引用,在要new出這個類的時候判斷下該引用是否為空就可以了。但這樣做會提高類間的耦合度,而且會在不同類間出現重複程式碼。明明是你自己只能有一個例項這件事,卻還得別人幫你做,這顯然不好,所以要讓類自己去讓自己只可以有一個例項。所以我們只能從它的構造器入手。我們按照如下步驟思考:

       1.首先不能讓其他的類隨便呼叫它的構造器,怎麼辦呢?我們可以將它的構造器改為私有的(可以麼?可以!)

       2.構造器程式設計私有,那就只能它自己的例項可以呼叫構造器了,但是呼叫構造器之前哪來的例項(雞生蛋問題)?

       3.錯了,可以使用靜態方法去呼叫私有的構造器。剩下的就是呼叫前判斷下物件是否存在了。如下圖:

      

   這裡uniqueInstance要定義為靜態變數,這樣它的生命週期和類的生命週期一樣,而不是和例項變數一樣生命週期是物件的生命週期。

   這樣呼叫getInstance方法時先判斷uniqueInstance是否為空,為空則建立物件,並將uniqueInstance

指向該物件,方法返回該引用。下次getInstance被呼叫的時候,uniqueInstance已經不是空了,所以直接返回之前new出來的物件,這樣每次呼叫getInstance都返回同一個物件。

   來看看官方對單例模式的定義:

   確保一個類只有一個例項,並提供一個全域性訪問點。

故事好像到這裡該結束了。no。這只是在單執行緒的情況下,一個類使用這種方式確實只可以產生一個物件,但是多執行緒情況呢?當兩個執行緒同時訪問getInstance方法時,uniqueInstance可都是空的哦。。

   不用怕,java對於多執行緒帶來的資源使用問題早已經有辦法。我們可以使用同步給getInstance方法加鎖:

  

    看,當一個執行緒進入getInstance方法時,另一個執行緒只能在門等待。直到第一個執行緒執行完方法後,第二個執行緒進入getInstance方法時uniqueInstance已經不為空了。所以也得到了我們想要的結果。

    但是這樣每次呼叫getInstance方法都要使用同步,這很消耗系統開銷的哦。對此我們有三種不同的方案實現:

    1.假如對效能要求不高的應用程式來說,那麼直接使用上面同步的方法就行了。

    2.如果對效能要求高的話,可以在類載入入虛擬機器的時候就將物件new出來,像這樣:

    

   這樣確保在任何執行緒進入的時候就已經有物件了。

   3.使用“雙重檢查加鎖”,只在物件不存在的時候使用同步一次。

     

    可能大家有疑問為什麼有兩次執行if(uniqueInstance == null)。原因是這樣的,當第一次兩個執行緒都通過第一個if(uniqueInstance == null)時,因為同步,只有一個執行緒可以進入synchronized後的程式碼,當第一個執行緒執行完方法後,第二個執行緒進入synchronized後的程式碼時如果不對uniqueInstance進行判斷,那麼執行緒照樣會執行new物件的程式碼。