設計模式之單例模式
單例模式
“單例(Singleton)”簡單理解就是單一實例,全局最多隻能創建一個該類的實例對象,該類提供得到該實例的全局訪問點,通常是getInstance()
這個靜態方法。可能你會疑問了,那我想得到對象,我new不就行了?對不起,這個類的構造方法修飾符是private,沒給你new的機會啊。那好吧。。。我們來看一下最簡單的例子:
public class Singleton {
private static Singleton mInstance; // Singleton模式全局唯一的實例對象
private static int count = 0; // 代表當前類被實例化的次數
//私有構造方法,讓你new無可new
private Singleton() {
count++;
System.out.println("啊,被實例化了" + count);
}
/**
* 全局提供的靜態方法訪問點
* @return 全局唯一的Singleton實例對象
*/
public static Singleton getInstance() {
if (mInstance == null) {
mInstance = new Singleton();
}
return mInstance;
}
/**
* 打印一下當前對象的hashcode
*/
public void printHashCode() {
System.out.println("hashCode--->" + this.hashCode());
}
}
接下來,看看測試類:
public class Test {
public static void main(String[] args) {
// Singleton singleton = new Singleton(); //這個肯定報錯的
Singleton st1 = Singleton.getInstance(); //這個嘛就是我們想要的了
st1.printHashCode();
Singleton st2 = Singleton.getInstance(); //再來一個
st2.printHashCode();
}
}
你猜結果:
啊,被實例化了1
hashCode--->366712642
hashCode--->366712642
沒有出乎你的想象吧,這兩個實例的hashcode是相同的,而且構造方法只被調用了一次。注意點:
1. getInstance()這個方法必須是靜態的;
2. 內部的對象mInstance也必須是靜態的,不然靜態方法怎麼訪問非靜態成員呢?
3. 構造方法必須是private修飾的,不然可以被各種new,全局可就有數不清的Singleton對象了(好奇怪)。
你以爲這樣就完了?好吧,單線程中,這樣確實差不多了哈,但是一遇到多線程,你可就要仔細仔細考慮了,來看個示意圖吧
public static Singleton getInstance() {
if (mInstance == null) { //①
mInstance = new Singleton();//②
}
return mInstance;
}
當兩個線程同時執行到①這兒,是不是就有機會創造不同的對象了呢?答案是肯定的啊!
我們把測試類中主方法內容改一改:
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
Singleton st = Singleton.getInstance();
st.printHashCode();
}
};
new Thread(r).start();//來一個新線程
new Thread(r).start();//再來一個新線程
//你還可以多來幾個新線程,看看結果亂成什麼樣子,一定不會讓你失望的
}
看到結果,我是平靜的,完全意料之中
啊,被實例化了2
啊,被實例化了2
hashCode--->358411109
hashCode--->1980094282
那該怎麼解決呢?想必你已經想到了,線程同步加synchronized
關鍵字,那我們不妨試試,public synchronized static Singleton getInstance()
僅僅多加了這個關鍵字,其他啥都變,再試試,結果一定是您期望的那樣,這兒就不貼出來了,就讓客官自己動手寫寫看效果吧。
擴展
談到這兒,基本概念也應該知道了吧,那我們再擴展擴展唄,把傳說中的“餓漢模式”、“懶漢模式”說說唄。
這兩種都是單例模式,只是具體實現方式不同。
懶漢模式
其實前面寫的代碼就是懶漢模式,說他懶,是因爲:每次你調用getInstance()
方法的時候,它纔會去判斷該唯一實例對象是否存在,不存在則new出來返回給你。
餓漢模式
說它‘餓’是因爲它真的很餓,你餓了嗎?我好像有點餓了。。。好吧,不知道說到哪兒去了。餓漢預先把對象new出來(就像人,餓了,先吃飽,好像比喻不怎麼恰當,沒關係啦)。上代碼吧
public class LazySingleton {
//類加載的時候,就預先new出來
private static LazySingleton mInstance = new LazySingleton();
private static int count = 0;
private LazySingleton() {
System.out.println("count--->" + (++count));
}
//直接返回對象
public static LazySingleton getInstance() {
return mInstance;
}
public void printHashCode() {
System.out.println("hashcode--->" + this.hashCode());
}
}
這個好處是:即使多線程,也不用考慮同步的問題,因爲類加載的時候就創建了唯一的對象,要得到實例時,直接返回即可。
好吧,看似很簡單的單例模式,其實也並不簡單,對於餓漢模式,還有一些可以優化的地方,採用“雙重檢查加鎖”,在getInstance()
中減少使用同步 。 具體請參考下面:
《Head First 設計模式(中文版)》 page182
題外話
這一段時間,一直在忙另外一個項目,所以沒有更新,不過,以後打算還是要堅持規律的更新一下吧。MarkDown編輯器還不是很熟,慢慢用着用着就好了吧,畢竟以前也沒用過,也沒花時間去學,我承認我有點懶了。
這篇沒有講故事,因爲單身和單例就比較配哦,沒有故事可講,嘻嘻。