設計模式之—單例模式(Singleton)-Java實現 一文中實現了簡單的單例模式,直接給出一個Singleton的簡單實現,因為我相信你已經有這方面的一些基礎了。我們姑且把這具版本叫做1.0版。既然單例模式的目標是只能產生一個執行個體,即整個系統中只能出現一個類的執行個體。那為何我們不把
private static Singleton instance = new Singleton();
定義為final呢?如此便有了第二個版本:
public class Singleton {private static final Singleton instance = new Singleton();private Singleton(){}public static Singleton getInstance(){return instance;}}
單例模式有兩種模式:餓漢式和懶漢式
上述為餓漢式,下面我們來研究一下餓漢式的存在的問題:前預先載入了。但是這種提前載入的方式會在多線程(高並發)環境下造成麻煩。如果private static final Singleton singleton = new Singleton();中的構造方法涉及到非同步網路資料交換如讀取伺服器配置或者資料庫,則此構造過程可能會被作業系統打斷而沒有完成載入,其他訪問singleton執行個體的線程度髒,而且錯誤很難查到。
下面來說說懶漢式的Singleton首先還是先上代碼:
public class Singleton { private static Singleton singleton = null; private Singleton() { } public static Singleton getInstance() { if (singleton== null) { singleton= new Singleton(); } return singleton; } }
在上面的執行個體中,我想再說明一下Singleton的特點:
私人(private)的建構函式,表明這個類是不可能形成執行個體了。這主要是怕這個類會有多個執行個體。
靜態getInstance()獲得執行個體,注意這個方法是在new自己,因為其可以訪問私人的建構函式,所以他是可以保證執行個體被建立出來的。在getInstance()中,先做判斷是否已形成執行個體,如果已形成則直接返回,否則建立執行個體。
所形成的執行個體儲存在自己類中的私人成員中。我們取執行個體時,只需要使用Singleton.getInstance()就行了。
上面的這個程式存在比較嚴重的問題,因為是全域性的執行個體,所以,在多線程情況下,所有的全域共用的東西都會變得非常的危險,這個也一樣,在多線程情況下,如果多個線程同時調用getInstance()的話,那麼,可能會有多個進程同時通過 (singleton== null)的條件檢查,於是,多個執行個體就建立出來,並且很可能造成記憶體泄露問題。下面就是我們要說的Singleton改進,熟悉多線程的朋友已經知道怎麼辦了——“我們需要線程互斥或同步”,沒錯,改進版如下所示:
public class Singleton { private static Singleton singleton = null; private Singleton() { } public static Singleton getInstance() { if (singleton== null) { synchronized (Singleton.class) { singleton= new Singleton(); } } return singleton; } }
現在看來使用了Java的synchronized方法,沒有問題了吧?!但是還是有問題!為什麼呢?如果有多個線程同時通過(singleton== null)的條件檢查(因為他們並行運行),雖然我們的synchronized方法會協助我們同步所有的線程,讓我們並行線程變成串列的一個一個去new,那不還是一樣的嗎?同樣會出現很多執行個體。看來,還得把那個判斷(singleton==
null)條件也同步起來。
public class Singleton { private static Singleton singleton = null; private Singleton() { } public static Singleton getInstance() { synchronized (Singleton.class) { if (singleton== null) { singleton= new Singleton(); } } return singleton; } }
我們把getInstance()方法也synchronized,這樣在多線程下應該沒有什麼問題了,Singleton在多線程下的確沒有問題了,因為我們同步了所有的線程。但是還是有點小問題,我們本來只是想讓new這個操作並行就可以了,現在,只要是進入getInstance()的線程都得同步,重點,建立對象的動作只有一次,後面的動作全是讀取那個成員變數,這些讀取的動作不需要線程同步啊。這樣的作法感覺非常不合理,為了一個初始化的建立動作,居然讓所有的讀操作都同步,嚴重影響效能!
現在我們線上程同步前還得加一個(singleton== null)的條件判斷,如果對象已經建立了,那麼就不需要線程的同步了。
public class Singleton { private static Singleton singleton = null; private Singleton() { } public static Singleton getInstance() { if (singleton== null) { synchronized (Singleton.class) { if (singleton== null) { singleton= new Singleton(); } } } return singleton; } }
說明:
第一個條件是說,如果執行個體建立了,那就不需要同步了,直接返回就好了。不然,我們就開始同步線程。
第二個條件是說,如果被同步的線程中,有一個線程建立了對象,那麼別的線程就不用再建立了。
當然這個版本的單例模式,代碼看似很醜陋卻解決了線程同步的問題。
無論代碼寫的多麼嚴謹、有多好,其只能在特定的範圍內工作,超出這個範圍就要出Bug了。
例如:代碼運行在多個JVM下照樣也會出現多個執行個體;如果我們的這個Singleton類是一個關於我們程式配置資訊的類。我們需要它有序列化的功能,那麼,當還原序列化的時候,我們將無法控制別人不多次還原序列化。不過,我們可以利用一下Serializable介面的readResolve()方法.
import java.io.Serializable;public class Singleton implements Serializable {………………………………protected Object readResolve() { return getInstance(); } }
此外Class Loader類裝載器是用來把類(class)裝載進JVM的。JVM規範定義了兩種類型的類裝載器:啟動內裝載器(bootstrap)和使用者自訂裝載器(user-defined class loader)。 在一個JVM中可能存在多個ClassLoader,每個ClassLoader擁有自己的NameSpace。一個ClassLoader只能擁有一個class物件類型的執行個體,但是不同的ClassLoader可能擁有相同的class對象執行個體,這時可能產生致命的問題。如ClassLoaderA,裝載了類A的類型執行個體A1,而ClassLoaderB,也裝載了類A的對象執行個體A2。邏輯上講A1=A2,但是由於A1和A2來自於不同的ClassLoader,它們實際上是完全不同的,如果A中定義了一個靜態變數c,則c在不同的ClassLoader中的值是不同的。
於是,如果面對著多個Class Loader會怎麼樣?多個執行個體同樣會被多個Class Loader建立出來,我們怎麼可能在我的Singleton類中操作Class Loader啊?是的,你根本不可能。在這種情況下,規範就起到作用了,規定——“保證多個Class Loader不會裝載同一個Singleton”。
以上論證了:無論代碼寫的多麼嚴謹、有多好,其只能在特定的範圍內工作,超出這個範圍就要出Bug了。