單例模式通常有兩種,餓漢單例和懶漢單例。
餓漢單例模式:
public class Singleton {
private static Singleton singleton = new Singleton();
private int flag;
public int getFlag() {
return flag;
}
public void setFlag(int flag) {
this.flag = flag;
}
private Singleton() {
}
public static Singleton getInstance() {
return singleton;
}
懶漢單例模式
public class Singleton2 {
private static Singleton2 singleton;
private Singleton2(){
}
public static synchronized Singleton2 getInstance(){
if(singleton == null){
singleton = new Singleton2();
}
return singleton;
}
餓漢單例模式中有一個 flag成員,以及相應的get和set方法。這不是單例模式必須的。方便後文做測試。
可以看到,它們都有一個共同點,那就是都有一個靜態的成員,且該成員就是自己。同樣,它們都有一個靜態方法,去返回自己的實例。
這樣做有什麼意思呢,根據我自身的體會,剛開始學的時候,會不明白,這個模式怎麼就能返回一個自己的實例,而且,在初始化實例的時候,對象本身有一個自己這個對象的方法,難道不會陷入死循環麼,自己實例化的成員對象,又實例化一個。
出現這樣的疑問,是自己基礎不牢固的問題。
其實是這樣的
類定義中不能包含自己本身的對象,否則會引起像無限遞歸的問題,而靜態成員屬於類,而不屬於對象,靜態成員的作用域屬於類,但不佔類的大小,不屬於類的對象,內存在全局存儲區。
靜態成員是不屬於類的實例的,它是類的在編譯的時候初始化放在全局變量裏面的。
我們總是把類比喻成 一座大樓的圖紙,而實例對象就是建造出來的大樓。但是靜態成員是已經存在的東西,他不屬於當時實際建造出來的那個大樓,而是圖紙本身帶有的東西。
繼續
因爲已經類的成員是靜態的了,而且是私有的,所以,想要獲得這個靜態對象,就必須通過類的方法返回。所以我們需要一個可以 return Singleton類型的方法。
這就是實現單例模式的基本思路。
那麼懶漢和餓漢有什麼區別呢,其實看名字也能瞭解。
餓漢是不管你什麼時候用,我都在程序編譯的時候,就生成一個靜態對象放在全局變量區裏面(就好像餓了很久的人迫不及待地去做吃東西)
懶漢是你什麼時候用,我什麼時候給你實例一個對象,我不會在程序編譯的時候生成對象(就好像一個有拖延症的人,等到deadline來的時候纔會去做)
既然這樣,我們可以思考一個問題,餓漢因爲迫不及待的去做(編譯時就有了實例),所以當我們想要得到實例的時候,我們可以放心大膽的拿來用。
那麼在懶漢模式,我們怎麼知道有沒有已經生成的實例的呢?
可以輕而易舉地想到:加個if語句判斷一下,如果這個靜態成員爲null,那麼就實例化。
這樣做其實是會出現問題的:如果很多人在同時使用了這個方法,而此時靜態成員卻爲null,那麼系統會生成多於一個的靜態實例(事實上會報錯)。所以,我們
給方法加一個 synchronized,表示這個方法只能同時被一個線程使用。
一個簡單的測試方法:
public class testSingleton {
public static void main(String[] args){
Singleton s = Singleton.getInstance();
s.setFlag(3);
Singleton s1 = Singleton.getInstance();
System.out.println("s1的flag值爲"+s1.getFlag());
}
思路:既然返回同一個單例,那麼我在第一個引用中存入一個數值給一個變量,然後通過getInstance()給予第二個引用單例,在第二個引用中獲取這個變量,如果這個變量的數值和我存的一樣,那麼就證明這是同一個實例。
2015.11.6 補充:單例模式其實就是將聲明和實例化分開 ,讓類把類的實例化掌握在自己的手裏,至於調用者調用哪個實例,都是類自己說了算,所以可以在getInstance方法裏面添加各種限制。由此,我想到了生活中類似的場景:公司的員工需要用到各種辦公文具,一開始公司要求員工自己去買,買了之後拿發票報銷,採用單例模式之後,員工不再自己去買,而是去向公司申請,至於公司是給新的文具,還是舊的文具,都是公司自己說了算。