單例模式介紹
- 所謂類的單例設計模式,就是采取一定的方法保證整個軟體系統中對某個類隻能存在一個對象執行個體,并且該類隻提供一個取得其對象執行個體的方法。
- 單例模式有八種方式
- 餓漢式(靜态常量)
- 餓漢式(靜态代碼塊)
- 懶漢式(線程不安全)
- 懶漢式 (線程安全,同步方法)
- 懶漢式(線程不安全,同步代碼塊)
- 雙重檢查
- 靜态内部類
- 枚舉
餓漢式(靜态常量)
步驟如下:
- 構造器私有化
- 類的内部建立一個私有靜态常量對象
- 向外暴露一個靜态的公共方法。
代碼如下:
/**
* @author fangyajun
* @description 餓漢式(靜态常量)
* @since 2019/11/28
*/
public class SingleTon01 {
private SingleTon01(){}
private static final SingleTon01 INSTANCE = new SingleTon01();
public static SingleTon01 instance() {
return INSTANCE;
}
}
分析說明:
- 優點:這種寫法比較簡單,就是在類裝載的時候完成執行個體化,避免線程同步的問題。
- 缺點:在類裝載的時候完成執行個體化,沒有達到Lzay Loading的效果,如果自始至終都沒有用過這個執行個體,就會造成記憶體浪費。
- 這種方式基于classloder機制避免了多線程同步的問題,不過instance在類裝載的時候就執行個體化了,在單例模式中大多都是調用instance方法,但是導緻内裝載的原因有很多種,是以不能确定有其他方法導緻類裝載,這個時候初始化就是餓漢式的。
- 結論:這種單例模式可用,但可能造成記憶體浪費
餓漢式(靜态代碼塊)
代碼如下:
class SingleTon02 {
private SingleTon02(){}
private static SingleTon02 instance;
static {
instance = new SingleTon02();
}
public static SingleTon02 instance() {
return instance;
}
}
分析說明:
- 這種方式和第一種方式類似,隻不過将類的執行個體化放在了靜态代碼塊中,也是在類的裝載的時候完成執行個體化,優缺點和上面一樣。
- 結論:這種單例模式可用,但是可能造成記憶體浪費
懶漢式(線程不安全)
代碼如下:
public class SingleTon03 {
private SingleTon03() {}
private static SingleTon03 singleTon03;
public static SingleTon03 getInstance() {
if (singleTon03 == null) {
singleTon03 = new SingleTon03();
}
return singleTon03;
}
}
分析說明:
- 起到了Lazy Loading的效果,但是隻能在單線程下使用。
- 如果在多線程下,一個線程進入了if (singleTon03 == null)判斷語句塊,還未來得及往下執行,另一個線程也進入了這個判斷語句,這時候便會産生多個執行個體,是以多線程下不可使用這個方式
- 結論:在實際開發過程中,不要使用這種方式
懶漢式(線程安全,同步方法)
代碼如下:
public class SingleTon04 {
private SingleTon04() {}
private static SingleTon04 singleTon03;
// 加入了同步鎖,同時隻能有一個線程進入方法内部
public static synchronized SingleTon04 getInstance() {
if (singleTon03 == null) {
singleTon03 = new SingleTon04();
}
return singleTon03;
}
}
分析說明:
- 解決了多線程先線程不安全的問題
- 此種方法在多線程下,同時隻能有一個線程進入方法内部,效率太低。而其實這個方法隻執行一次執行個體化代碼就夠了,後面想擷取得改類的執行個體。直接return就行了,而該方式不管怎麼樣同時隻能一個線程進入方法内部,效率低
- 結論:在實際開發中,不推薦使用這種方式
懶漢式(同步代碼塊,線程不安全)
代碼如下:
public class SingleTon05 {
private SingleTon05() {}
private static SingleTon05 singleTon03;
public static SingleTon05 getInstance() {
if (singleTon03 == null) {
synchronized (SingleTon05.class) {
singleTon03 = new SingleTon05();
}
}
return singleTon03;
}
}
分析說明:
- 這種方法其實是想對上面那種進行改進,改為同步代碼塊,但是仔細分析我們發現,這種同步其實不能起到線程同步的作用,假如一個線程進入了if (singleTon03 == null)判斷語句塊還未來得及往下執行,另一個線程也進入了這個判斷語句還未進入同步代碼塊,這時候變會産生多個執行個體。
- 結論:在實際開發中,不能使用這種方式
懶漢式(雙重檢查)
代碼如下:
public class SingleTon06 {
private SingleTon06() {}
private static SingleTon06 singleTon06;
public static SingleTon06 getInstance() {
if (singleTon06 == null) {
synchronized (SingleTon06.class) {
if (singleTon06 == null) {
singleTon06 = new SingleTon06();
}
}
}
return singleTon06;
}
}
分析說明:
- 這方式是對上一種同步代碼塊線程不安全的改進,我們進行了2次if (singleTon06 == null)判斷,進而保證了線程安全。
- 這樣執行個體化代碼塊隻用執行一次,後面再次通路時,判斷if (singleTon06 == null)就直接return傳回已經執行個體化好的對象,進而達到線程安全和Lazy Loading的目的,還有解決了同步方法效率的問題,
- 線程安全,延遲加載,效率較高
- 結論:在實際開發中,
這種單例設計模式
推薦使用
懶漢式(靜态内部類)
代碼如下:
public class SingleTon07 {
private SingleTon07() {}
private static class SingletonInstance {
private static final SingleTon07 SINGLE_TON_07 = new SingleTon07();
}
public static SingleTon07 instance() {
return SingletonInstance.SINGLE_TON_07;
}
}
分析說明:
- 這種方式采用了類裝載機制來保證初始化執行個體時隻有一個線程。
- 靜态内部類方式在SingleTon07類被裝載的時候不會被立即執行個體化,而是在需要執行個體化的時候,調用instance方法才會裝載内部類SingletonInstance ,從未完成SingleTon07的執行個體化,達到了Lazy Loading的目的。
- 類的靜态屬性隻會在第一次加載類的時候初始化,JVM幫助我們保證了線程的安全性,在類進行初始化的時候,别的線程是無法進入的。
- 優點: 避免了線程不安全,利用了靜态内部類特點實作延遲加載,效率高
- 結論: 推薦使用
枚舉
代碼如下:
enum SingleTon08 {
INSTANCE;
}
測試如下:
class Test01 {
public static void main(String[] args) {
SingleTon08 instance = SingleTon08.INSTANCE;
SingleTon08 instance2 = SingleTon08.INSTANCE;
System.out.println(instance == instance2);
}
}
結果輸出為:
true
分析說明:
- 這是借助JDK1.5中添加的的枚舉來實作的單例模式,不僅能避免多線程同步問題,而且還能防止反複重新建立對象,
- 這種方式是是提倡使用的。
- 結論:推薦使用
總結:
- 單例模式保證了系統記憶體中該類隻存在一個對象,節省了系統資源,對于一些頻繁建立銷毀的對象,使用單例模式可以提高系統性能
- 當想執行個體化一個單例類的時候,必須要記住使用相應的擷取對象的方法,而不new
- 單例模式的使用場景:相應頻繁建立和銷毀的對象,建立對象時耗時過多或者耗費資源過多(重量級對象),但有經常用到的對象,工具類對象,頻繁通路資料庫和檔案的對象(如:資料源,session工廠等)
- 針對以上8種單例模式,第1,2,6,7,8在實作開發中都是可以使用的,可以根據具體場景選擇相應的單例模式。對于3,4,5類型的單例模式不推薦使用,最好也不要使用。