標籤:儲存 完成 jvm 懶漢 blank ret 序號 [] mil
1. 單例模式的定義
單例模式(Singleton Pattern)是一個比較簡單的模式。其原始定義例如以下:Ensure a class has only one instance, and provide a global point of access to it. 即確保僅僅有一個執行個體,並且自行執行個體化並向整個系統提供這個執行個體。單例模式的通用類例如以所看到的:
Singleton類稱為單例類,通過使用private的建構函式確保了在一個應用中僅僅產生一個執行個體。而且是自行執行個體化的(在Singleton中自己new Singleton())。單例模式的通用代碼例如以下(這樣的也稱為餓漢式單例):
/****************** 單例模式:程式清單1 ***************************/public class Singleton {private static Singleton instance = new Singleton(); //1.自己內部new一個private Singleton() { //2.私人建構函式,防止被執行個體化}//3.提供一個公用介面。用來返回剛剛new出來的對象 public static Singleton getInstance() { return instance;}public void test() {System.out.println("singleton");}}/********************************************************************/
2. 單例模式存在的安全執行緒問題
上面是一個經典的單例模式程式。且這個程式不會產生線程同步問題。由於類第一次載入的時候就初始化了instance。可是單例模式還有其它的實現方式,就有可能會出現線程同步問題。請看以下的範例:
/* * 這樣的方式就是非安全執行緒了(懶漢式單例) */public class Singleton {private static Singleton instance = null;private Singleton() {}public static Singleton getInstance() {if(instance == null) {instance = new Singleton();} return instance;}}
為什麼會出現安全執行緒問題呢?假如一個線程A運行到instance = new Singleton()。但還沒有獲得對象(對象的初始化是須要時間的),第二個線程B也在運行。運行到推斷instance == null時。那麼線程B獲得的條件也是真,於是也進入執行個體化instance了,然後線程A獲得了一個對象,線程B也獲得了一個對象,在記憶體中就存在了兩個對象了!
解決安全執行緒問題的方法有非常多,比方我們能夠在getInstance()方法前面加上synchronizedkeyword來解決,例如以下:
public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } 可是synchronizedkeyword鎖住的是這個對象,這種使用方法在效能上會有所下降,由於每次調用getInstance()時都要對對象上鎖。其實。僅僅要在第一次建立對象的時候加鎖,後面建立完了就不須要了。所以我們能夠做進一步的改進。例如以下:
public static Singleton getInstance() { if (instance == null) { synchronized (instance) { if (instance == null) { instance = new Singleton(); } } } return instance; } 我們將synchronizedkeyword加到內部,也就是說當調用的時候是不須要加鎖的,僅僅有在instance == null的時候且建立對象的時候再加鎖,這樣要比上面的那種方式好。可是這種方式還是有可能會產生安全執行緒問題,由於JVM中建立對象和賦值操作是分開進行的,即instance = new Singleton()這句是分兩步進行的。過程是這種:JVM會為先給Singleton執行個體分配一個空白的記憶體。並賦值給instance成員,可是此時JVM並沒有開始初始化這個執行個體。然後再去new一個Singleton對象賦給instance。這就會導致線程問題了。比方A線程進入synchronized代碼塊了,運行完了instance = new Singleton()後結束代碼塊。可是此時還沒有真正初始化,這是線程B進來了,發現instance不為null。於是就立刻返回該instance(事實上是沒有初始化好的),然後B就開始使用該instance,卻發現沒初始化,於是就出問題了。
所以要解決這樣的“懶漢式”單例的線程問題,一種建議使用上面的程式清單1的方式,即使用”餓漢式“單例。還有一種,在實際中,也可以用內部類來維護單例的實現。JVM內部的機制可以保證當一個類被載入的時候。這個類的載入過程是線程相互排斥的。
這樣。當我們第一次調用getInstance()方法的時候。JVM可以幫我們保證instance執行個體僅僅被建立一次,而且會保證把賦值給instance的記憶體初始化完成。見例如以下代碼:
/****************** 單例模式:程式清單2 ****************************/public class Singleton { private Singleton() { //私人構造方法,防止被執行個體化 } /*使用一個內部類來維護單例 */ private static class SingletonFactory { private static Singleton instance = new Singleton(); } public static Singleton getInstance() { //擷取執行個體 return SingletonFactory.instance; } /* 假設該對象被用於序列化。能夠保證對象在序列化前後保持一致 */ public Object readResolve() { return getInstance(); } }/********************************************************************/
3.單例模式的複製
上面分析了單例模式的安全執行緒問題,還有個問題就是須要考慮單例模式中對象的複製問題。
在java中。對象預設是不能夠被複製的,可是若實現了Cloneable介面。並實現了clone方法,則能夠直接通過對象複製方式建立一個新對象,對象複製不是調用類的構造方法,所以即使是私人的構造方法,對象仍然是能夠被複製的。
可是在普通情況下,單例類非常少會主動要求被複製的,所以解決該問題最好的方法就是單例類不要實現Cloneable介面就可以。
4. 單例模式的擴充
假設一個類能夠產生多個對象且數量不受限制,是很easy的,直接new就是了。
可是假設使用單例模式,可是要求一個類真能產生兩三個對象呢?這樣的情況該怎樣實現?針對這樣的情況,我們就須要在單例類中維護一個變數,用來表示執行個體的個數。並且還須要一些容器來儲存不同的執行個體以及執行個體相應的屬性,例如以下:
/*************************** 單例模式的擴充:程式清單3 ************************************/public class Singleton {//定義最多能產生的執行個體數量private static int maxNumOfInstance = 3;//儲存每一個執行個體的名字private static ArrayList<String> nameList = new ArrayList<String>();//儲存每一個執行個體對象private static ArrayList<Singleton> instanceList = new ArrayList<Singleton>();//當前執行個體的索引private static int indexOfInstance = 0;//靜態代碼塊,在類載入的時候初始化2個執行個體static {for(int i = 0; i < maxNumOfInstance; i++) {instanceList.add(new Singleton("instance" + (i+1)));}}private Singleton() {}private Singleton(String name) { //帶參數的私人建構函式nameList.add(name);}//返回執行個體對象public static Singleton getInstance() {Random random = new Random();//隨機挑選一個執行個體indexOfInstance = random.nextInt(maxNumOfInstance);return instanceList.get(indexOfInstance);}public void test() {System.out.println(nameList.get(indexOfInstance));}}/******************************************************************************************/ 我們寫一個測試程式看看結果就知道了:
public class SingletonTest {public static void main(String[] args) {int num = 5;for(int i = 0; i < num; i++) {Singleton instance = Singleton.getInstance();instance.test();}}} 這樣我們就實現了用單例模式產生固定數量的執行個體。測試結果輸出例如以下:
instance1instance1instance2instance3instance3
5. 單例模式的優缺點
長處:
1.在記憶體中僅僅存在一個執行個體。全部減小誒村的開支。特別是一個對象須要頻繁的建立和銷毀時,並且建立或銷毀時效能又無法最佳化。單例模式的優勢就很明顯。
2.減小了系統的效能開銷。當一個對象的產生須要比較多的資源時,如讀取配置、產生依賴對象時,則能夠通過在應用啟動時直接產生一個單例對象。然後用永久駐留在記憶體中。
3.能夠避免對資源的多重佔用,如寫檔案動作。因為僅僅有一個執行個體存在記憶體中,避免對同一個資源檔的同一時候寫操作。
4.單例模式能夠在系統設定全域的訪問點。最佳化和共用資源訪問,比如能夠設計一個單例類,負責全部資料表的映射處理。
缺點:
1.單例模式沒有介面。擴充非常難。若要擴充,除了改動代碼基本上沒有另外一種途徑能夠實現
2.單例模式對測試是不利的,在並行開發環境中,假設單例模式沒有完畢,是不能進行測試的。
6. 單例模式的應用情境
在一個系統中,要求一個類僅有一個對象時,能夠採用單例模式:
1. 要求產生唯一序號的環境。
2. 在整個項目中須要一個共用訪問點或共用資料,比如一個web頁面上的訪問量,能夠不用每次重新整理都把記錄存到資料庫。可是要確保單例安全執行緒。
3. 建立一個對象須要消耗的資源過多。如要訪問IO和資料庫等資源。
4. 須要定義大量的靜態常量和靜態方法(如工具類)的環境。能夠採用單例模式。當然也能夠直接聲明為static方式。
Spring中也用到了單例模式,每一個Bean預設就是單例的,這樣做的有點事Spring容器能夠管理這些Bean的生命期,決定什麼時候建立出來,什麼時候銷毀,銷毀的時候要怎樣處理等等。
假設採用非單例模式(Prototype類型),則Bean初始化後的管理交給J2EE容器了,Spring容器就不在跟蹤管理Bean的生命週期了。
單例模式就討論這麼多吧,如有錯誤之處,歡迎留言指正~
相關閱讀:http://blog.csdn.net/column/details/des-pattern.html
_____________________________________________________________________________________________________________________________________________________
-----樂於分享,共同進步!
-----很多其它文章請看:http://blog.csdn.net/eson_15
【java設計模式】之 單例(Singleton)模式