美團一面:會單例模式嗎,寫個單例看看?(8大單例模式實現方式總結)

ITPUB社群發表於2023-11-13

來源:mikechen的網際網路架構

一位小夥伴前幾天參加美團一面,被問到了這道題目:會單例模式嗎,用靜態內部類寫個單例。

在 Java 中,單例模式是開發時應用最多的設計模式。例如,常見的 Spring 預設建立的 bean ,就是單例模式。

在大廠面試中,單例模式也是高頻常考。透過這個題目,可以考察很多知識點。例如:單例模式的執行緒安全問題,列舉的實現、類載入機制、synchronized 的原理.....。

由於單例模式沒回答好,小夥子最終敗倒美團一面,錯失了大廠機會。

詳細面試過程就不細說了,更重要的是,我們不能在同一個地方跌倒兩次。

我們今天就來介紹單例模式的 8 種實現方式看完本文,面試再被問到單例模式的實現,就能給到面試官滿意的答案了。

本文目錄:

  1. 單例模式的定義

  2. 單例模式的使用原因

  3. 單例模式的 8 種實現方式

  4. 單例模式的 4 大應用場景

  5. 單例模式的優點、缺點

  6. 單例模式的選型參考思路

  7. 總結

美團一面:會單例模式嗎,寫個單例看看?(8大單例模式實現方式總結)

大家好,我是mikechen,本文是設計模式系列篇之一。為了方便大家學習,我已將本文內容歸納到《深入淺出設計模式》PDF,一共約 2+萬字,81頁,圖文並茂非常詳細。

夯實基礎、複習備面都用得上,需要的同學文末自取。

美團一面:會單例模式嗎,寫個單例看看?(8大單例模式實現方式總結)


01
  單例模式的定義

單例模式(Singleton Pattern),又稱為單子模式,屬於建立型模式。

單例模式是一種常見的設計模式,確保一個類只有一個例項,並提供一個全域性訪問點以獲取該例項。

美團一面:會單例模式嗎,寫個單例看看?(8大單例模式實現方式總結)

單例模式的特點:

  • 單例類只能有一個例項。

  • 單例類必須自己建立自己的唯一例項。

  • 單例類必須給所有其他物件提供這一例項。

 

02
  單例模式的使用原因

單例模式:

  • 保證了全域性物件的唯一性。在整個應用中有、且只有一個例項,並提供一個全域性訪問點,以獲取該例項。


  • 避免了因為建立了多個例項造成資源的浪費,且多個例項由於多次呼叫、容易導致結果出現錯誤。


示例:



















public class Printer {    private static Printer printer =null;//建立一個私有的全域性變數    /*     * 如果有多執行緒併發訪問時,上鎖,讓其排隊等候,一次只能一人用。     */    public static synchronized Printer getPrinter(){        if(printer==null){//如果為空,建立本例項            printer = new Printer();        }        return printer;    }    /*     * 構造私有化,保證在系統的使用中,只有一個例項     */    private Printer(){
   }}

使用單例模式:

  • 單例模式向外提供了一個可被訪問的例項化的物件。如果沒有該物件,printer 類建立一個。


  • 如果遇到多執行緒併發訪問,加上關鍵字 Synchronized 上鎖,讓沒有持有該物件的類處於等待狀態。


  • 當前持有該 printer 的執行緒任務結束之後,處於等待中的執行緒才能逐個去持有該例項,去操作其方法。


透過單例模式,讓多執行緒處於等待的狀態,一個一個去解決,節約了記憶體,也提高了執行的效率。

不使用單例模式:

如果多執行緒訪問,printer 就會給要請求的類,分別在記憶體中 new 出一個 printer 物件,讓這些請求的類去做 print 方法。

這樣會佔用大量記憶體,導致系統執行變慢。


03
  單例模式的 8 種實現

常見的單例模式實現方式有 8 種。

我們先一覽全貌,然後再逐一瞭解。

美團一面:會單例模式嗎,寫個單例看看?(8大單例模式實現方式總結)


1.   餓漢模式

美團一面:會單例模式嗎,寫個單例看看?(8大單例模式實現方式總結)


1.1  餓漢式:靜態常量

實現最簡單、執行緒安全。

例項在類載入時就被建立,避免了執行緒同步問題。

但是,例項在類載入時就被建立,沒有達到 Lazy Loading 的效果。

如果不需要、且從未使用過該例項,就會浪費記憶體。










public class Singleton {    private static final Singleton instance = new Singleton();
   private Singleton() { }
   public static Singleton getInstance() {        return instance;    }}


1.2  餓漢模式:靜態程式碼塊

和第 1 種方式的主要區別,是在類裝載時,就執行靜態程式碼塊中的程式碼,初始化類的例項。















public class Singleton {
    private static Singleton instance;
   static {          instance = new Singleton( );    }
  private Singleton() { }
  public static Singleton getInstance()  {           return inatance;   }}


2.  懶漢模式

美團一面:會單例模式嗎,寫個單例看看?(8大單例模式實現方式總結)

2.1   懶漢模式

執行緒不安全,在多執行緒環境中不能使用該方式,只能在單執行緒環境中使用。

如果在多執行緒環境中,一個執行緒進入了 if (singleton == null)  判斷語句塊,還沒往下執行,另一個執行緒也透過了這個判斷語句,這時就會產生多個例項。

















public class Singleton {
    private static Singleton singleton;
    private singleton () { }
    public static Singleton getInstance ()  {  
        if (singleton == null ) {               singleton =  new Singleton  ( ) ;
        }         return singleton;
    }}

 

2.2  懶漢模式:同步方法

執行緒安全,但方法進行同步的效率極低。

雖說做個執行緒同步,能解決第 3 種方法中的執行緒不安全問題。

但是,每個執行緒需要獲得類的例項時,執行 getInstance() 方法都需要進行同步,效率極低。













public class Singleton {    private static Singleton instance;
   private Singleton() { }
   public static synchronized Singleton getInstance() {        if (instance == null) {            instance = new Singleton();        }        return instance;    }}


2.3  懶漢模式:同步程式碼塊

執行緒安全,但沒有起到執行緒同步的作用,可能產生多個例項。

例如:

一個執行緒進入了 if (singleton == null) 判斷語句塊,還沒往下執行,另一個執行緒也透過了這個判斷語句,這時便會產生多個例項。















public class Singleton {
     private static Singleton singleton;
     private  Singleton ( ) { }
     public static Singleton getInstance( ) {            if (singleton == null ) {            synchronized (Singleton.  class)  {                 singleton = new Singleton( ) ;            }            return singleton;       }}


 

3.  雙重檢查鎖(Double-Checked Locking)

美團一面:會單例模式嗎,寫個單例看看?(8大單例模式實現方式總結)

執行緒安全、延遲載入、效率較高。

使用 volatile 關鍵字,來保證底層指令執行順序。

入口處判斷 null,可以省去每次加鎖的耗費,提升效能。

但由於第一次載入反應稍慢,以及 Java 記憶體模型的原因,偶爾還是會失敗,在高併發環境下存在一定的缺陷。


















































/**
* Double Check Lool (DCL)實現單例
*
* @author mikechen
*/
public class SingletonDCL {
   private volatile static SingletonDCL instance = null;


   //構造方法私有
   private SingletonDCL() {
   }


   public static SingletonDCL getInstance() {
       //進行兩次非空判斷  ,第一層是為了避免不必要的同步
       if (instance == null) {
           //獲取Singleton3.class的鎖,避免例項化多次
           synchronized (SingletonDCL.class) {
               if (instance == null) {
                   instance = new SingletonDCL();
               }
           }
       }
       return instance;
   }
}


 

4.  靜態內部類

美團一面:會單例模式嗎,寫個單例看看?(8大單例模式實現方式總結)

避免了執行緒不安全,延遲載入,效率高。

在類內部有一個靜態內部類,由於靜態內部類的靜態屬性,僅在第一次載入類時初始化。

確保執行緒安全,也能夠保證單例物件的唯一性,同時也延遲了單例的例項化。

缺點是無法傳參,在類進行初始化時,其它執行緒無法進入。




































/**
* 靜態內部類實現單例模式
*
* @author mikechen
*/
public class SingletonStatic {
   private SingletonStatic() {
   }


   public static SingletonStatic getInstance() {
       return SingLineHolder.instance;
   }


   private static class SingLineHolder {
       private static final SingletonStatic instance = new SingletonStatic();
   }


}

靜態內部類與餓漢模式的相同之處

都採用了類裝載的機制,來保證初始化例項時只有一個執行緒。

靜態內部類與餓漢模式的不同之處:

  • 餓漢式:只要 Singleton 類被裝載就會例項化,沒有 Lazy-Loading 的作用。


  • 靜態內部類:在 Singleton 類被裝載時,並不會立即例項化。而是在需要例項化時,呼叫 getInstance 方法,才會裝載SingletonInstance 類,從而完成 Singleton 的例項化。

 

5.   列舉

美團一面:會單例模式嗎,寫個單例看看?(8大單例模式實現方式總結)

多執行緒安全,寫法非常簡潔,自動支援序列化機制,絕對防止多次例項化。

缺點是不能透過 reflection attack 來呼叫私有構造方法。




















/**
* 列舉實現單例模式
*
* @author mikechen
*/
public enum  SingletonEnum {
   INSTANCE;
   SingletonEnum() {}
   public void getName() {}
}


 

04
  單例模式的應用場景

單例模式通常用於想要控制例項數目、節省系統資源的場景中。

一些常見的應用場景:

  •  工具類物件;


  •  頻繁訪問資料庫或檔案的物件;


  • 需要頻繁的進行建立和銷燬的物件;


  • 建立物件時耗時過多、或耗費資源過多,但又頻繁要用到的物件。


美團一面:會單例模式嗎,寫個單例看看?(8大單例模式實現方式總結)


05
  單例模式的優缺點

單例模式的優點:

  • 避免對資源的多重佔用;


  • 簡化物件管理,提供全域性訪問點,方便在整個應用程式中共享例項;


  • 提供了對唯一例項的受控訪問。

 

單例模式的缺點:

  • 由於單例模式中沒有抽象層,因此單例類的擴充套件有很大的困難;


  • 不適用於變化的物件,如果同一型別的物件總是要在不同的用例場景發生變化,單例就會引起資料的錯誤,不能儲存彼此的狀態;


  • 單例類職責過重,在一定程度上違背了單一職責。

 

06
  單例模式選型參考

單例模式的選型參考:

  • 列舉方式通常被認為是最佳實踐,因為它既簡潔、又執行緒安全;


  • 一般情況下使用餓漢式,不使用懶漢式、懶漢式(同步方法);


  • 只有在要明確實現 lazy loading 效果時,才會使用靜態內部類方式;


  • 如果有其他特殊的需求,可以考慮使用雙重檢查鎖。


以上,只是提供一些參考思路。

大家在實際應用時,要結合每種實現方式的特點、具體使用場景、以及實際需求合理選型。


總結
  

以上,就是單例模式的全部詳解。

透過本文,我們瞭解並掌握了單例模式的核心知識點,包括:單例模式的 8 種實現方式、4 大應用場景、以及單例模式的選型參考思路等。

在 Java 中,單例模式是開發時應用最多的設計模式。在大廠面試中,單例模式也是高頻常考,非常重要。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024420/viewspace-2994906/,如需轉載,請註明出處,否則將追究法律責任。

相關文章