定義 單例模式(Singleton Pattern)是最簡單的一種設計模式。 英文原話是:Ensure a class has only one instance,and provide a global point of access to it. 意思是:確保一個類只有一個實例,而且自動實例化並 ...
定義
單例模式(Singleton Pattern)是最簡單的一種設計模式。
英文原話是:Ensure a class has only one instance,and provide a global point of access to it.
意思是:確保一個類只有一個實例,而且自動實例化並向整個系統提供這個實例。
單例模式的主要作用是確保一個類只有一個實例存在。單例模式可以用在建立目錄,資料庫連接等需要單線程操作的場合,用於實現對系統資源的控制。
分類
Java語言的特點使得在Java中實現單例模式通常有兩種表現形式:
餓漢式單例類:類載入時,就進行對象實例化。
懶漢式單例類:第一次引用類時,才進行對象實例化。
餓漢式單例類
餓漢式代碼如下:
public class Singleton{ private static Singleton m_instance = new Singleton(); //構造方法私有,保證外界無法直接實例化 private Singleton(){ } //通過該方法獲得實例對象 public static Singleton getInstance(){ return m_instance; } }
上面這段代碼中,在類載入時,靜態變數m_instance會被初始化,此時類的私有構造函數會被調用,單例類的唯一實例就被創建出來。單例類中最重要的特點是類的構造函數是私有的,從而避免外界利用構造函數直接創建出任意多的實例。另外,構造函數是私有的,因此該類不能被繼承。
懶漢式單例類
懶漢式單例類與餓漢式單例類相同的是,類的構造函數是私有的;不同的是,懶漢式單例類在載入時不會將自己實例化,而是在第一次被調用時將自己實例化。
懶漢式代碼如下:
public class Singleton{ private static Singleton _instance = null; //構造方法私有,保證外界無法直接實例化 private Singleton(){ } //方法同步 synchronized public static Singleton getInstance(){ if(_instance==null){ _instance = new Singleton(); } return _instance; } }
上面這段代碼中,懶漢式單例類中對靜態方法getInstance()進行同步,以確保多線程環境下只創建一個實例。例如:如果getInstance()方法未被同步,並且線程A和線程B同時調用此方法,則執行if(_instance==null)語句時都為真,線程A和線程B會分別創建一個對象,在記憶體中就會出現兩個對象,這樣就違反了單例模式;單使用synchronized關鍵字進行同步後,就不會出現這種情況了。
餓漢式單例類與懶漢式單例類之間的區別:
1.懶漢式單例類在被載入時實例化,而懶漢式單例類在第一次引用時實例化。
2.從資源利用上說,餓漢式單例類比懶漢式單例類要差一些(因為餓漢式一開始就會實例化一個對象占用系統資源),但從速度和反應時間角度來講,則餓漢式單例類比懶漢式單例類好一些。
3.餓漢式單例類可以在Java中實現,但不易在C++中實現。GoF在提出單利模式的概念是舉的例子是懶漢式的,他們的書影響較大,以至於Java中單例類的例子大多是懶漢式的。實際上,餓漢式單例類更符合Java語言本身的特點。
單例對象的優點
1.由於單例模式在記憶體中只有一個實例,減少了記憶體的開支,特別是當一個對象需要頻繁地被創建、銷毀而且創建或銷毀的性能又無法優化時,單例模式的優勢就非常明顯。
2.由於單例模式值生成一個實例,所以減少了系統的性能開銷,當一個對象的產生需要比較多的資源時,如讀取配置文件、產生其他依賴對象時,可以在啟動時直接產生一個單例對象,然後永久駐留記憶體的方式來解決。
3.單例模式可以避免對資源的多重占用。例如:一個寫文件動作,由於只有一個實例存在於記憶體中,避免了對同一個資源文件的同時寫操作。
4.單例模式可以在系統設置全局訪問點,優化和共用資源訪問。
單例模式的缺點
1.單例模式無法創建子類,且擴展困難。若要擴展,除了修改代碼以外基本上沒有第二種途徑可以實現。
2.單例模式對測試不利。在並行開發環境中,如果採用單例模式的類沒有完成,程式是不能進行測試的;單例模式的類通常不會實現介面,這也妨礙了使用mock的方式虛擬一個對象。
3.單例模式與單一職責原則有衝突。一個類應該只實現一個邏輯,而不關心它是否是單例的,是否使用單例模式取決於環境,單例模式。
單例模式的使用場景
在一個系統中,如果要求一個類有且僅有一個實例,當出現多個實例時就會造成不良反應,則此時可以採用單例模式。典型場景如下:
1.要求生成唯一序列號的環境。
2.在整個項目中需要一個訪問點或共用數據。(如web頁面上的計數器)
3.創建一個對象需要消耗的資源過多,如訪問IO和資料庫等資源。
4.需要定義大量的靜態常量和靜態方法(如工具類)的環境,可以採用單例模式。
這是一個誤導人的線程例子,經過修改後.就變得很簡單.
public class CounterSingleton {
//懶漢式載入 private static CounterSingleton singleton = new CounterSingleton(); //私有構造,防止生成對象 private CounterSingleton() { } //獲取類 public static CounterSingleton getInstance() { return singleton; }
//對象的私有變數 public int count = 0; //懶漢式對象的方法 public synchronized void inc() { //加訪問量 count++; //System.out.println(count); }
public int getCount() {
return count;
}
} public class Counter { public static void main(String[] args) { long time_s=System.currentTimeMillis(); //同時啟動1000個線程,去進行i++計算,看看實際結果 for (int i = 0; i < 1000; i++) { new Thread(new Runnable() { @Override public void run() { CounterSingleton.getInstance().inc(); } }).start(); } //這裡每次運行的值都有可能不同,顯示結果可能不為1000(inc方法沒有sychonized的話,count就是真的不是1000了) System.out.println("運行結果:CounterSingleton.count=" + CounterSingleton.getInstance().getCount()); System.out.println("耗時:"+(System.currentTimeMillis()-time_s)); } }