面試官:寫幾種你知道的單例模式!Java實現單例模式有幾種方式?3,5?餓漢,懶漢!?
在面試中經常會遇到單例模式的問題,動不動就是你知道幾種單例模式,請手寫幾種你知道的單例模式,爲了能徹底釐清該問題,本文詳細闡述了單例模式的最全的八種寫法以及茴香豆的茴字的四種寫法(手動狗頭),並比較其存在的優劣,如果本文對你有所幫助的話點贊哦親。
-
什麼是設計模式(Design Pattern)?
設計模式是人類在解決各類問題時所總結出來的有用的經驗,它不是軟件工程中特有的概念。具體來說設計模式是一種思想,是解決某類問題的通用方案,代表了最佳實踐,也是前人經過相當長時間的試驗和錯誤而分析總結出來的。
-
分類:
設計模式總共分爲三類,總共23種
- 創建型 :單例模式、抽象工廠模式、原型模式、建造者模式、工廠模式
- 結構型 :適配器模式、橋接模式、裝飾者模式、組合模式、外觀模式、享元模式、代理模式
- 行爲型 : 模板方法模式、命令模式、訪問者模式、迭代器模式、觀察者模式、中介者模式、備忘錄模式、解釋器模式、狀態模式、策略模式、職責鏈模式(責任鏈模式)
正文開始
-
單例模式
單例模式就是通過某種實現方式使得在整個軟件系統中,對某個類只能存在一個對象實例,並且該類只提供一個取得該對象實例的靜態方法。
應用場景:
1、比如Windows的資源管理器,同一臺Windows上不能同時打開兩個資源管理器。
2、數據庫連接池,因爲數據庫連接是一種數據庫資源,爲了節省打開或者關閉數據庫連接所引起的損耗,單例模式的使用就尤爲重要。
3、Spring對bean的管理,可以通過scope選擇該bean是單例(singleton)還是多例(prototype)。
綜上所述,單例模式在軟件工程中使用還是比較頻繁的,爲了能在面試中能夠脫穎而出讓面試官中意你,並且工作中遇到能熟練運用,下面主要通過代碼的方式來詳細介紹單例模式的八種實現方式。
單例模式的八種實現方式
- 餓漢式(靜態常量)
- 餓漢式(靜態代碼塊)
- 懶漢式(線程不安全)
- 懶漢式(線程安全,同步方法實現)
- 懶漢式(線程安全,同步代碼塊實現)
- 雙重檢查(Double Check)
- 靜態內部類
- 枚舉
-
Java實現
1. 餓漢式(靜態常量)
步驟:
①、私有化構造器,防止外部通過new
生成實例
②、提供一個同類型的常量,用於接收內部實例
③、提供一個public
方法供外部調用以獲取生成的實例
public class Singleton {
//1、私有化構造器,防止外部new產生實例
private Singleton() {
}
//2、定義一個Singleton類型的常量,用於接收Singleton內部實例
private final static Singleton instance = new Singleton();
//3、定義一個public方法提供給外部調用,獲取實例對象
public static Singleton getInstance() {
return instance;
}
}
-
優點:
實現較爲簡單,定義爲常量即在類加載的時候就完成實例化。通過
ClassLoader
機制避免了線程安全的問題。 -
缺點:
根據
JVM
加載類的方式得知類加載器並不需要等到某個類被主動使用時才加載,JVM
允許類加載器在預料到某個類將要被使用時就預先加載它,所以類加載是一種不確定行爲,而這種餓漢式的實現方式會在類加載的時候就完成實例化,所以不能達到懶加載(Lazy Loading
)的效果。可能造成內存浪費。
2. 餓漢式(靜態代碼塊)
步驟:
①、私有化構造器,防止外部通過new
生成實例
②、定義static
實例對象,用於接收靜態代碼塊生成的內部實例
③、通過靜態代碼塊創建對象實例
④、提供一個public
方法供外部調用以獲取生成的實例
public class Singleton {
//1、私有化構造器,防止外部new產生實例
private Singleton(){
}
//2、定義實例對象準備接收
private static Singleton instance;
//3、通過靜態代碼塊創建對象實例
static {
instance = new Singleton();
}
//4、定義一個public方法提供給外部調用,獲取實例對象
public static Singleton getInstance(){
return instance;
}
}
- 該方法優缺點同上(可能造成內存浪費)
3. 懶漢式(線程不安全)
步驟:
①、私有化構造器,防止外部通過new
生成實例
②、定義static
實例對象,用於接收靜態代碼塊生成的內部實例
③、提供一個public
方法供外部調用以獲取生成的實例,首先判斷該實例是否已經生成,如果已經生成則直接使用,否則再實例化生成。
public class Singleton {
//1、私有化構造器,防止外部new產生實例
private Singleton(){
}
//2、定義一個static實例對象接收對象實例
private static Singleton instance;
//3、定義一個public方法提供給外部調用,獲取實例對象
public static Singleton getInstance(){
if (null == instance)
instance = new Singleton();
return instance;
}
}
-
優點:
實現簡單且可以實現懶加載(Lazy Loading)
-
缺點:
if (null == instance)
該句代碼在多線程環境存在線程不安全問題,可能生成多個實例,破壞了單例模式。實際開發中不要使用這種方式。
4. 懶漢式(線程安全,同步方法實現)
步驟:
①、私有化構造器,防止外部通過new
生成實例
②、定義static
實例對象,用於接收靜態代碼塊生成的內部實例
③、提供一個public
方法供外部調用以獲取生成的實例並通過synchronized
加鎖,首先判斷該實例是否已經生成,如果已經生成則直接使用,否則再實例化生成。
public class Singleton {
//1、私有化構造器,防止外部new產生實例
private Singleton(){
}
//2、定義一個static實例對象接收對象實例
private static Singleton instance;
//3、定義一個public方法提供給外部調用,通過synchronized加鎖,獲取實例對象
public static synchronized Singleton getInstance(){
if (null == instance)
instance = new Singleton();
return instance;
}
}
-
優點:
解決了懶漢式了線程安全的問題。
-
缺點:
由於通過synchronized加鎖,多線程環境下獲取對象實例效率極低。實際開發不推薦使用。
5. 懶漢式(線程不安全,同步代碼塊實現)
步驟:
①、私有化構造器,防止外部通過new
生成實例
②、定義static
實例對象,用於接收靜態代碼塊生成的內部實例
③、定義一個public方法提供給外部調用,通過synchronized同步代碼塊加鎖,獲取實例對象。
public class Singleton {
//1、私有化構造器,防止外部new產生實例
private Singleton(){
}
//2、定義一個static實例對象接收對象實例
private static Singleton instance;
//3、定義一個public方法提供給外部調用,通過synchronized同步代碼塊加鎖,獲取實例對象
public static Singleton getInstance(){
if (null == instance)
synchronized (Singleton.class){
instance = new Singleton();
}
return instance;
}
}
-
缺點:
通過synchronized代碼塊加鎖,依然線程不安全。實際開發不推薦使用。
6. 雙重檢查
步驟:
①、私有化構造器,防止外部通過new
生成實例
②、定義volatile
實例對象,保證生成實例對象的可見性,並用於接收靜態代碼塊生成的內部實例
③、定義一個public方法提供給外部調用,獲取實例對象,同時再在內部進行兩次判斷,第二次進行synchronized代碼塊加鎖,一旦實例化成功,鎖外的線程即可通過volatile
獲得實例。
public class Singleton {
//1、私有化構造器,防止外部new產生實例
private Singleton(){
}
//2、定義volatile實例對象,保證數據可見性
private static volatile Singleton instance;
//3、定義一個public方法提供給外部調用,獲取實例對象,同時再在內部進行進行synchronized代碼塊加鎖
public static Singleton getInstance(){
if (null == instance)
synchronized (Singleton.class){
if (null == instance)
instance = new Singleton();
}
return instance;
}
}
-
優點:
實現了懶加載(
Lazy Loading
)雙重檢查,保證線程安全
實例化代碼只用執行一次,多線程後面再次訪問時可通過
volatile
通知。
推薦使用。
7. 靜態內部類
步驟:
①、私有化構造器,防止外部通過new
生成實例
②、通過靜態內部類的屬性獲得Singleton
實例
③、定義一個public
方法提供給外部調用,獲取實例對象。
public class Singleton {
//1、私有化構造器,防止外部new產生實例
private Singleton(){
}
//2、通過靜態內部類的屬性獲得Singleton實例
private static class SingletonInstance{
private static Singleton INSTANCE = new Singleton();
}
//3、定義一個public方法提供給外部調用,獲取實例對象
public static Singleton getInstance(){
return SingletonInstance.INSTANCE;
}
}
-
優點:
實現了懶加載(
Lazy Loading
)只有當
getInstance()
方法第一次被調用時,纔會去導致虛擬機加載SingletonInstance
類,而類的靜態屬性只會在第一次加載類的時候初始化,所以這種方式通過JVM
的加載機制以保證線程安全。
推薦使用。
8. 枚舉
步驟:
①、通過枚舉類屬性實現
public enum Singleton {
INSTANCE;
public void hello(){
System.out.println("hello");
}
}
-
優點:
線程安全,實現簡單
防止反序列化重新創建新的對象
強烈推薦使用。
總結
單例模式保證了 系統內存中該類只存在一個對象,節省了系統資源,對於一些需要頻繁創建銷燬的對象,使用單例模式可以極大地提高系統性能,例如數據庫連接池,session
工廠等。