java設計模式中的單例模式是最爲常見的一種之一,並且網上也有許多資料可以查閱,說說我對這個模式的認識與理解,以學習筆記的形式展示給大家,也方便自己以後對知識的回顧。
單例模式用一句話來說明就是 可以 讓一個類的實例有且只有一個,控制了類的實例化的個數,使用這種設計模式,有效的避免了實例化對象時出現重複性,節省了內存空間又提高實例化對象的時間效率,由於整個程序使用的都是同一個實例對象,就可以更好的統一管理控制這個對象在程序中的狀態。
下面就從單例模式的5種表現形式來說明:分別是 懶漢式 ,餓漢式,雙重鎖形式,靜態內部類,枚舉。
1.餓漢式實現代碼如下:
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton(){}
public static Singleton newInstance (){
return instance;
}
}
現在來說明一下爲什麼這麼寫以及這樣實現單例的優缺點
爲了實現一個類只能實例化一個對象,肯定需要將構造函數設置爲私有的,來保證其他類不能實例化這個類,實例化變量instance設置爲私有的,也是爲了防止外部其他類對其進行訪問,只能有本類提供的公有的靜態方法返回給調用者,靜態方法可以通過Singleton來調用,不能調用多少次都是同一個instance,由於Instances變量設置成了靜態的,是屬於類的,同時也體現出來一個‘餓’,類加載時就已經將這個實例創建出來了。
使用這個的好處:不會在多線程的情況下,出現創建多個實例的情況,因爲這個類在加載後,這個實例就被創建出來了,也正是這個原因,、
不好之處:就是 我們在不使用這個實例的時候也會創建出來,造成了內存空間上的浪費。
再說一下使用這個懶漢式的場景:一般是在是實例時,內存開銷比較小的,以及在初始化後需要用到該實例,這樣的情況下,如果是實例化對象的內存開銷大,在特殊場合需要用到就不適合用這個懶漢式,這時就需要用到餓漢式來解決這個問題。
2 懶漢式單例
public class Singleton1 {
private static Singleton1 instance = null;
private Singleton1(){}
public static synchronized Singleton1 getInstance(){
if(null== instance){
instance = new Singleton1();
}
return instance;
}
分析說明:由上可知,很多寫法和餓漢式類似,就不一一解釋了,現在我們主要理解好這個“懶”字,主要體現在,外部調用getInStances這個時,先判斷有沒有這個實例,沒有的話,就創建出來,有了就直接返回,它不會在類加載後就創建出來,這就彌補了餓漢式的缺點,減小了內存的開銷,由於它會出現多線程可能會實例化多個對象問題的出現就對獲取實例的方法加了同步鎖。在對某個單例使用的次數少,並且創建單例消耗的資源較多的情況下,可以考慮使用這種單例模式。
3.雙重鎖的形式
public class Singleton {
private static volatile Singleton instance = null;
private Singleton(){}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
對於雙重鎖定的理解:
上面的加了鎖的懶漢式的雖然解決了線程併發,延遲加載的問題,但是存在性能上的問題,多次調用getInstance(),消耗的性能會增加。 使用雙重鎖定,我們知道靜態代碼塊中的內容大部分情況下是不會被執行到的,但是也有情況下,我們有2條線程同時判斷了"instance==null" 的時候成立,這時如果我們進行第二次判斷,就有可能出現實例化2個對象出來,而進行雙重驗證就避免了這個問題,這樣寫的第一個做“instance==null”判斷就是進行了性能的優化,因爲不用每次都去執行同步代碼塊裏面的內容,提到了程序的性能,這是對懶漢式的缺點的彌補,同時我們要注意volatile對實例變量的修飾,valatile的一個語義是禁止指令重排序優化,也就保證了instance變量被賦值的時候對象已經是初始化過的現象。
4.靜態內部類
public class Singleton{
private static class SingletonHolder{
public static Singleton instance = new Singleton();
}
private Singleton(){}
public static Singleton newInstance(){
return SingletonHolder.instance;
}
}
分析說明:這種靜態內部同樣使用了類加載機制,和我們第一個說到的餓漢式一樣,不會出現多線程的情況下出現不同的實例,保證了線程的安全,不同的是,通過靜態的內部類,也實現了延遲加載,就是如果不用靜態類去調用instance 去實例化該對象。因此這種單例模式用的比較對多。
5 枚舉實現
public enum Singleton{
instance;
public void whateverMethod(){}
}
這種方式在實際的開發中很少用到,但是它能解決以上四種在使用單例模式的各種問題,主要包括
1)需要額外的工作來實現序列化,否則每次反序列化一個序列化的對象時都會創建一個新的實例。
2)可以使用反射強行調用私有構造器(如果要避免這種情況,可以修改構造器,讓它在創建第二個實例的時候拋異常)。
而枚舉類很好的解決了這兩個問題,使用枚舉除了線程安全和防止反射調用構造器之外,還提供了自動序列化機制,防止反序列化的時候創建新的對象。
總結一下:在使用單例模式時,優先選用第三,四種,能過解決大部分的問題,懶漢式和餓漢式存在許多的問題,而最後一種枚舉來實現,用的人很少,對於很多人來說很生疏。