應用場景
在學習單例設計模式前,我想應該先去了解這種模式是在什麼樣的背景下產生的,這有利於後續學習時的理解。記得我自己最先真正使用到單例模式,是在進行數據庫連接的時候,當時在做原生項目的時候,因爲需要經常訪問數據庫,就會常常打開和關閉數據庫連接,這種方式會導致項目的效率下降。在這種場景下,如果使用一個類,來對數據庫資源的連接進行封裝,保證在整個項目中,只有一個該類提供的對象,就可以避免上述的問題。所以,單例模式的作用也就體現出來了,當然它的應用場景還有許多,例如項目日誌輸出,統計網站在線人數等。
定義
單例模式最初的定義出現於《設計模式》(艾迪生維斯理,1994):“保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。”在Java中單例模式的定義是:“一個類有且僅有一個實例,並且自行實例化向整個系統提供。”
實現思路
單例模式分爲餓漢式和懶漢式兩種,兩者實現的思路相同:
1.在單例類中,提供一個私有靜態屬性,用於接收實例化對象;
2.在單例類中,構造方法必須私有化,防止在類外進行實例化;
3.在單例類中,向類外提供一個獲取類對象的公有靜態方法。
代碼實現
餓漢式單例模式實現
餓漢式單例模式:在系統加載類的時候就會創建類的對象,並保存在類中。它的特點是線程安全,以空間換時間。
public class Singleton {
//1、私有靜態屬性
private static Singleton instance = new Singleton();
//2、私有構造方法
private Singleton() {
System.out.println("***創建Singleton類對象***");
}
//3、公有靜態方法
public static Singleton getInstance() {
return instance;
}
//測試
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2);
}
}
//程序執行結果
***創建Singleton類對象***
true
懶漢式單例模式實現
懶漢式單例模式:在系統加載類的時候不創建類的對象,直到第一次使用類的對象時纔會進行創建。它的特點是線程不安全,以時間換空間。
public class Singleton {
//1、私有靜態屬性
private static Singleton instance = null;
//2、私有構造方法
private Singleton() {
System.out.println("***創建Singleton類對象***");
}
//3、公有靜態方法
public static Singleton getInstance() {
//判斷類對象是否爲空,若爲空則創建類對象
if(instance == null){
instance = new Singleton();
}
return instance;
}
//測試
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2);
}
}
//程序執行結果
***創建Singleton類對象***
true
懶漢式單例模式是存在線程風險的,下面我們來測試一下,是不是像說的那樣真的存在線程風險?
public class Singleton {
//1、私有靜態屬性
private static Singleton instance = null;
//2、私有構造方法
private Singleton() {
System.out.println("【" + Thread.currentThread().getName() + "】***創建Singleton類對象***");
}
//3、公有靜態方法
public static Singleton getInstance() {
//判斷類對象是否爲空,若爲空則創建類對象
if(instance == null){
instance = new Singleton();
}
return instance;
}
public void print() {
System.out.println("**執行print()方法**");
}
//測試
public static void main(String[] args) {
//創建三個線程對象並運行
for(int i = 0; i < 3; i++){
new Thread(() -> {
//在線程運行過程中,獲取Singleton類對象
Singleton.getInstance().print();
}, "線程對象 - " + i).start();
}
}
}
//程序執行結果
【線程對象 - 0】***創建Singleton類對象***
【線程對象 - 1】***創建Singleton類對象***
**執行print()方法**
【線程對象 - 2】***創建Singleton類對象***
**執行print()方法**
**執行print()方法**
通過程序的執行結果,發現當有了若干個線程對象,當前的程序就可以產生多個Singleton類的對象。顯然,這不符合單例模式的特點,因爲單例模式要求在程序的整體運行中只產生一個實例化對象。造成上述問題的關鍵是,代碼本身出現了不同步的情況,而解決的核心就在於進行同步處理。
懶漢式單例模式改進
實現代碼同步,我們需要使用到sychronized關鍵字。改進方法一,直接對公有靜態方法做同步處理:
public static synchronized Singleton getInstance() {
//判斷類對象是否爲空,若爲空則創建類對象
if(instance == null){
instance = new Singleton();
}
return instance;
}
//程序執行結果
【線程對象 - 0】***創建Singleton類對象***
**執行print()方法**
**執行print()方法**
**執行print()方法**
按照上述方法,我們的確實現了單例模式的要求,保證在程序整體運行過程中,單例類只提供一個實例化的對象。但是,使用這種方法做同步處理,效率會比較低,因爲在整個方法中只有一個地方需要做同步處理,下面我們使用同步代碼塊進行更合理的同步處理。
public static Singleton getInstance() {
//判斷類對象是否爲空,若爲空則創建類對象
if(instance == null){
//同步代碼塊,靜態方法中不允許使用this關鍵字,這裏使用類的Class對象
synchronized(Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
//程序執行結果
【線程對象 - 0】***創建Singleton類對象***
**執行print()方法**
**執行print()方法**
**執行print()方法**
通過對“判斷instance對象是否爲空”作同步處理,這種方法可以減小同步產生的代價,使程序的執行效率更高一些。
懶漢式單例模式實現,使用這種方法也被稱爲DCL(雙重檢查加鎖)。另外,爲了在程序性能上再做一點提升,可以在單例模式的屬性聲明上使用volatile關鍵字,例如:
private static volatile Singleton instance = null;
到達文末了,十分感謝小夥伴們的閱讀哈,如果您對我的文章有什麼意見或者建議,也歡迎您來進行評論,私信我也可以哈!
代碼如詩,小夥伴們和我一起努力加油,做持續學習者!:)