所謂的單例模式,就是採起必定的方法保證在整個的軟件系統中,對某個類只能存在一個對象實例,而且該類只提供一個取得其對象實例的方法(靜態方法)。好比,Hibernate的SessionFactory,它充當數據存儲源的代理,並負責建立Session對象。SessionFactory並非輕量級的,通常狀況下,一個項目一般只須要一個SessionFactory就夠了,這就會使用到單例模式。程序員
public class Singleton {
// 一、構造器私有化,防止new
private Singleton(){
}
// 二、類內部建立對象實例
private static final Singleton INSTANCE = new Singleton();
// 三、提供一個公有的靜態方法,返回實例對象
public static Singleton getInstance() {
return INSTANCE;
}
}
複製代碼
優缺點:數據庫
- 優勢:這種寫法比較簡單,就是在類裝載的時候就完成實例化。避免了線程同步問題。
- 缺點:在類裝載的時候就完成實例化,沒有帶到Lazy Loading的效果。若是從始至終從未使用過這個實例,則會形成內存的浪費。
- 這種方式基於classloder機制避免了多線程的同步問題,不過,instance在類裝載時就實例化,在單例模式中大多數都是調用getInstance方法,可是致使類裝載的緣由有不少種,所以不能肯定有其餘的方式(或者其餘的靜態方法)致使類裝載,這時候初始化instance就沒有達到Lazy Loading的效果。 *結論:這種方式可用,可是形成內存浪費。
public class Singleton {
private Singleton() {
}
private static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
複製代碼
優缺點:windows
- 起到了Lazy Loading的效果,可是隻能在單線程下使用。
- 若是在多線程下,一個線程進入了if(singleton == null)判斷語句塊,還將來得急往下執行,另外一個線程也經過了這個判斷語句,這時便會產生多個實例。因此在多線程環境下不可以使用這種方式。
- 結論:在實際開發中,不要使用這種方式。
public class Singleton {
private Singleton() {
}
private static Singleton instance;
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
複製代碼
優缺點:安全
- 解決了線程安全問題。
- 效率過低了,每一個線程在想獲取類的實例時候,執行getInstance()方法都要進行同步。而其實這個方法只執行一次實例化代碼就夠了,後面的想得到該類實例,直接return就好了。方法進行同步效率過低。
- 結論:在實際開發中,不推薦使用這種方式。
雙重檢驗鎖模式(double checked locking pattern),是一種同步塊加鎖的方法。程序員稱其爲雙重檢查鎖,由於會有兩次檢查instance == null,一次是在同步塊外,一次是在同步塊內。爲何在同步塊內還要在檢驗一次?由於可能會有多個線程一塊兒進行同步塊外的if,若是在同步塊內不進行二次檢查的話就會生成多個實例了。bash
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;
}
}
複製代碼
這段代碼看起來很完美,很惋惜,它是有問題的。主要在於instance = new Singleton()這句,這並不是是一個原子操做,事實上在JVM中這句話大概作了下面3件事。網絡
1) 給instance分配內存。session
2) 調用Singleton的構造函數來初始化成員變量。多線程
3)將instance對象指向分配的內存空間(執行完這步instance就爲非null了)。app
可是在JVM的即時編譯器中存在指令重排序的優化。也就是說上面的第二步和第三步的順序是不能保證的,最終的執行順序多是1-2-3,也多是1-3-2。可是在3執行完畢、2未執行以前,被線程二搶佔了,這時instance已是非null了(但卻沒有初始化),因此線程二會直接返回instance,而後使用,最後瓜熟蒂落地報錯。函數
解決方法:將instance變量聲明成volatile就能夠了。
public class Singleton {
private Singleton() {
}
private volatile static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
複製代碼
有些人認爲使用volatile的緣由是可見性,也就是能夠保證線程在本地不會存有instance的副本,每次都是去主內存讀取。但實際上是不對的。使用volitile的主要緣由是其另外一個特性:靜止指令重排序優化。也就是說,在volatile變量的賦值操做後面會有一個內存屏障(生成的彙編代碼上),讀操做不會被重排序到內存屏障以前。比例上面的例子,取操做必須在執行完1-2-3以後或者1-3-2以後,不存在執行到1-3而後取到值的狀況。從[先行發生原則]的角度理解的話,也就是對於一個volatile變量的寫操做都先行發生於後面對這個變量的讀操做(這裏的「後面」是時間上的前後順序)。
可是特別注意在Java 5之前的版本使用volatile的雙檢鎖仍是有問題的。其緣由是Java 5之前的JMM(Java內存模型)是存在缺陷的,即時將變量聲明成volatile也不能徹底避免重排序,主要是volatitle變量先後的代碼仍然存在重排序問題。這個volatile屏蔽重排序的問題在Java 5中才得以修復,因此在這以後才能夠放心使用volatile。
優缺點:
- Double-Check概念是多線程開發中常使用到的,如代碼中所示,咱們進行了兩次if(singleton == null)檢查,這樣就能夠保證線程安全了。
- 這樣,實例化代碼只用執行一次,後面再次訪問時,判斷if(singleton == null),直接return實例化對象,也避免的反覆進行方法同步。
- 線程安全;延遲加載;效率較高。
- 結論:在實際開發中,推薦使用這種方式。
public class Singleton {
private Singleton() {
}
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
複製代碼
優缺點:
- 這種方式採用了類裝載的機制來保證初始化實例時只用一個線程。
- 靜態內部類方式在SIngleton類被裝載時並不會當即實例化,而是在須要實例化時,調用getInstance方法,纔會裝載SingletonInstance類,從而完成Singleton的實例化。
- 類的靜態屬性只會在第一次加載類的時候初始化,因此在這裏,JVM幫助咱們保證了線程的安全性,在類進行初始化時,別的線程是沒法進入的。
- 優勢:避免了線程不安全,利用靜態內部類特色實現延遲加戰,效率高。
- 結論:推薦使用。
public enum Singleton {
INSTANCE;
}
複製代碼
優缺點:
- 這藉助JDK1.5中添加的枚舉來實現單例模式。不只能避免多線程同步問題,並且還能防止反序列化從新建立新的對象。
- 這種方式是Effective Java做者Josh Blocj提倡的方式。
- 結論:推薦使用。
- windows的Task Manager(任務管理器)就是典型的單例模式。
- windows的 Recycle Bin(回收站)。
- 網絡的計數器,通常也是採用單例模式實現,不然難以同步。
- 應用程序的日誌應用,通常都何用單例模式實現,這通常是因爲共享的日誌文件一直處於打開狀態,由於只能有一個實例去操做,不然內容很差追加。
- Web應用的配置對象的讀取,通常也應用單例模式,這個是因爲配置文件是共享的資源。
- 數據庫鏈接池的設計通常也是採用單例模式,由於數據庫鏈接是一種數據庫資源。數據庫軟件系統中使用數據庫鏈接池,主要是節省打開或者關閉數據庫鏈接所引發的效率損耗,這種效率上的損耗仍是很是昂貴的,由於何用單例模式來維護,就能夠大大下降這種損耗。
- 多線程的線程池的設計通常也是採用單例模式,這是因爲線程池要方便對池中的線程進行控制。
- 操做系統的文件系統,也是大的單例模式實現的具體例子,一個操做系統只能有一個文件系統。
- HttpApplication 也是單位例的典型應用。熟悉ASP.Net(IIS)的整個請求生命週期的人應該知道HttpApplication也是單例模式,全部的HttpModule都共享一個HttpApplication實例.
Runtime.class使用單例模式的代碼:
public class Runtime {
private static Runtime currentRuntime = new Runtime();
/**
* Returns the runtime object associated with the current Java application.
* Most of the methods of class <code>Runtime</code> are instance
* methods and must be invoked with respect to the current runtime object.
*
* @return the <code>Runtime</code> object associated with the current
* Java application.
*/
public static Runtime getRuntime() {
return currentRuntime;
}
/** Don't let anyone else instantiate this class */ private Runtime() {} } 複製代碼
- 當想實例化一個單例類的時候,必需要記住使用相應的獲取對象的方法,而不是使用new。
- 只能使用單例類提供的方法獲得單例對象,不要使用反射,不然將會實例化一個新對象。
- 不要作斷開單例類對象與類中靜態引用的危險操做。
- 多線程使用單例使用共享資源時,注意線程安全問題。
- 單例模式使用的場景:須要頻繁的進行建立和銷燬的對象;建立對象時耗時過多或耗費資源過多(即:重量級對象),但又常常用到的對象;有狀態的工具類對象;頻繁訪問數據庫或文件的對象(好比:數據源、session工廠等)。
特別聲明:一、如若文中有錯之處,歡迎大神指出。 二、文章是參考網上一些大神的文章,本身整理出來的,如如有侵權,可聯繫我刪除。