各類單例模式的比較以及建立方法

單例模式的初始化java


面向對象設計模式(OO Design)有不少, 其中很重要的模式是單例模式。 和單例模式相關的另外一個問題是這個單例在多線程環境下的實例化。 如何實例化的自己,也是另外一個OO設計模式,網上相關的討論不少,我作以下總結,供你們分享。設計模式


單例化的類有2個重要特徵:緩存

1)構造是私有的,這樣能夠保證外部沒法實例化這個類安全

2)提供一個靜態的方法,供外部調用以得到這個實例。多線程


單例子模式的設計,單例類的代碼基本以下:性能


public class SingletonClass {spa


public static SingletonClass getInstance() {線程

//如何返回這個實例,下面具體討論。設計

}面向對象設計模式

private SingletonClass() {

//私有構造防止外部實例化這個類。

}

}


getInstance()的實現,有如下幾個方案:


方案1:定義變量爲final

最簡單的方法是在這個類中定義一個靜態的final的變量,final變量保證是線程安全的。

private static final SingletonClass instance = new SingletonClass();

在getInstance()中直接返回這個變量。

public static SingletonClass getInstance() {

return instance;

}

但這種作法的缺點是, 當JVM在加載這個類時, 會去構造instance。換句話說, 還不知道後面程序用獲得用不到這個實例, 先構造了這個實例, 形成資源浪費。

咱們程序中,早些時間寫的程序,有這樣的寫法,但後面就逐漸沒有了。


方案2:懶惰構造(lazy instantiation)

lazy instantiation 又稱爲Initialization-on-demand holder idiom,顧名思義就是在用到時在構造這個實例。 這在維基百科(http://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom)有介紹。懶惰構造的簡單形式是

public synchronized static SingletonClass getInstance() {

if ( instance == null) {

instance = new SingletonClass(); 

}

return instance;

這個方案能夠在用到時才實例化。但最大的問題是: 爲了保證線程安全,須要加synchronized, 但synchronized有代價, 對單實例的實例化操做作synchronized,很不合算。去年,咱們曾對這個lazy instantiation作了改進,採用了後來在網上纔看到的所謂double checked locking 的設計模式。如今咱們的程序都基本是用double checked locking了。


方案3 double-check locking

double checked locking的形式以下:

public static SingletonClass getInstance() {

if ( instance == null) {

synchronized ( lock ) {

if ( instance == null) {

instance = new SingletonClass();

}

}

}

return instance;


對instance作了兩次判空,因此叫double-checked。 在instance不爲空後,就不用鎖了,因此效率比直接在方法前加synchronized要高得多。但這個方法有漏洞:Java Memory Model,將memory分紅main memory(或稱heap)和線程緩存(thread local memory)。執行一個線程時,從main memory讀取值保存到thread memory,線程執行結束後,再將thread local memory的值返回給main memory。這樣作的緣由是,線程緩存一般是CPU的緩存,速度要比main memory快得多。但這樣作的後果是,同一個變量在兩個不一樣的線程,看到的值是不同的(尤爲是一個線程修改了變量的值,另外一個線程有可能看不到這個值得變化)。這個問題在多線程程序中叫作visibility. 上述double check locking的問題就是,當一個線程分配空間給instance後,另外一個線程看到這個空間,因此當即返回。但instance中的值的變化,第二個線程並無看到。因此第二個線程返回的值是不完整的。在java 1.5後,volatile能夠解決visibility的問題。

方案4: double-checked locking + volatile

java 1.5後對volatile的定義有了很大的變化: 當一個變量聲明成volatile時,它能保證:instance只有在構造所有結束後(也即分配空間+給變量賦值所有結束), 纔會將變量複製回mainmemory, 而且是當即送回(不須要等到線程結束才送回), 而另外一個線程用instance時, 必定會去main memory取值, 不會用線程緩存的值。 有了這兩個保證後, 第2個線程要不就看不到instance已經生成, 要不就看到一個徹底生成的instance, 不會出現看到一個部分賦值的instance。 因此,在java 5後, 若是對instance增長volatile的定義, double check locking是安全有效的。 但java 4之前, 由於volatile沒有這個功能, 因此double check locking是有漏洞的。

這個方案的缺點是: 1)volatile變量比正常變量效率低(線程不能用高速的線程內存), 因此總體對效率是有影響, 但影響沒有synchronized大。2)這個方法讀起來費勁。


方案5:lazy holder模式。

lazy holder模式就是對方案1的改進,它不用synchronized,也不用volatile,因此沒有上述的開銷。lazy holder模式的代碼以下:


public class SingletonClass {


public static SingletonClass getInstance() {

return SingletonClassHolder.instance;

}

private SingletonClass() {

//私有構造防止外部實例化這個類。

}

private interface SingletonClassHolder {

public static final SingletonClass instance = new SingletonClass();

}


}

這個模式的重點就是將final的實例藏在一個內部的interface中。引進一個interface作holder的做用就是:當JVM載入SingletonClass這個類時,不會當即構造instance,

而方案1會。這個方法很簡單,也沒有開銷。


//////////////////////////////總結///////////////////////////////////////

單例模式的實例化,按性能高低排,依次是:

lazy holder > double checked locking > synchronized


如今咱們系統中已經使用了double checked locking,對這些地方,須要對變量加volatile

若是是新的代碼,要用lazy holder模式實例化。

相關文章
相關標籤/搜索