美團一面:會單例模式嗎,寫個單例看看?(8大單例模式實現方式總結)
來源:mikechen的網際網路架構
一位小夥伴前幾天參加美團一面,被問到了這道題目:會單例模式嗎,用靜態內部類寫個單例。
在 Java 中,單例模式是開發時應用最多的設計模式。例如,常見的 Spring 預設建立的 bean ,就是單例模式。
在大廠面試中,單例模式也是高頻常考。透過這個題目,可以考察很多知識點。例如:單例模式的執行緒安全問題,列舉的實現、類載入機制、synchronized 的原理.....。
由於單例模式沒回答好,小夥子最終敗倒美團一面,錯失了大廠機會。
詳細面試過程就不細說了,更重要的是,我們不能在同一個地方跌倒兩次。
我們今天就來介紹單例模式的 8 種實現方式,看完本文,面試再被問到單例模式的實現,就能給到面試官滿意的答案了。
本文目錄:
單例模式的定義
單例模式的使用原因
單例模式的 8 種實現方式
單例模式的 4 大應用場景
單例模式的優點、缺點
單例模式的選型參考思路
總結
大家好,我是mikechen,本文是設計模式系列篇之一。為了方便大家學習,我已將本文內容歸納到《深入淺出設計模式》PDF,一共約 2+萬字,81頁,圖文並茂非常詳細。
夯實基礎、複習備面都用得上,需要的同學文末自取。
單例模式(Singleton Pattern),又稱為單子模式,屬於建立型模式。
單例模式是一種常見的設計模式,確保一個類只有一個例項,並提供一個全域性訪問點以獲取該例項。
單例模式的特點:
單例類只能有一個例項。
單例類必須自己建立自己的唯一例項。
單例類必須給所有其他物件提供這一例項。
單例模式:
保證了全域性物件的唯一性。在整個應用中有、且只有一個例項,並提供一個全域性訪問點,以獲取該例項。
避免了因為建立了多個例項造成資源的浪費,且多個例項由於多次呼叫、容易導致結果出現錯誤。
示例:
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 方法。
這樣會佔用大量記憶體,導致系統執行變慢。
常見的單例模式實現方式有 8 種。
我們先一覽全貌,然後再逐一瞭解。
1. 餓漢模式
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. 懶漢模式
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)
執行緒安全、延遲載入、效率較高。
使用 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. 靜態內部類
避免了執行緒不安全,延遲載入,效率高。
在類內部有一個靜態內部類,由於靜態內部類的靜態屬性,僅在第一次載入類時初始化。
確保執行緒安全,也能夠保證單例物件的唯一性,同時也延遲了單例的例項化。
缺點是無法傳參,在類進行初始化時,其它執行緒無法進入。
/**
* 靜態內部類實現單例模式
*
* @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. 列舉
多執行緒安全,寫法非常簡潔,自動支援序列化機制,絕對防止多次例項化。
缺點是不能透過 reflection attack 來呼叫私有構造方法。
/**
* 列舉實現單例模式
*
* @author mikechen
*/
public enum SingletonEnum {
INSTANCE;
SingletonEnum() {}
public void getName() {}
}
單例模式通常用於想要控制例項數目、節省系統資源的場景中。
一些常見的應用場景:
工具類物件;
頻繁訪問資料庫或檔案的物件;
需要頻繁的進行建立和銷燬的物件;
建立物件時耗時過多、或耗費資源過多,但又頻繁要用到的物件。
單例模式的優點:
避免對資源的多重佔用;
簡化物件管理,提供全域性訪問點,方便在整個應用程式中共享例項;
提供了對唯一例項的受控訪問。
單例模式的缺點:
由於單例模式中沒有抽象層,因此單例類的擴充套件有很大的困難;
不適用於變化的物件,如果同一型別的物件總是要在不同的用例場景發生變化,單例就會引起資料的錯誤,不能儲存彼此的狀態;
單例類職責過重,在一定程度上違背了單一職責。
單例模式的選型參考:
列舉方式通常被認為是最佳實踐,因為它既簡潔、又執行緒安全;
一般情況下使用餓漢式,不使用懶漢式、懶漢式(同步方法);
只有在要明確實現 lazy loading 效果時,才會使用靜態內部類方式;
如果有其他特殊的需求,可以考慮使用雙重檢查鎖。
以上,只是提供一些參考思路。
大家在實際應用時,要結合每種實現方式的特點、具體使用場景、以及實際需求合理選型。
以上,就是單例模式的全部詳解。
透過本文,我們瞭解並掌握了單例模式的核心知識點,包括:單例模式的 8 種實現方式、4 大應用場景、以及單例模式的選型參考思路等。
在 Java 中,單例模式是開發時應用最多的設計模式。在大廠面試中,單例模式也是高頻常考,非常重要。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024420/viewspace-2994906/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 你真的會寫單例模式嗎——Java實現單例模式Java
- 結合 Android 看看單例模式怎麼寫Android單例模式
- 您的單例模式,真的單例嗎?單例模式
- 單例模式:5種實現方式單例模式
- 單例模式 – 單例登錄檔與 Spring 實現單例剖析單例模式Spring
- 五種方式實現 Java 單例模式Java單例模式
- PHP實現單例模式PHP單例模式
- golang實現單例模式Golang單例模式
- Rust實現單例模式Rust單例模式
- DCL單例模式中的缺陷及單例模式的其他實現單例模式
- JS中的單例模式及單例模式原型類的實現JS單例模式原型
- 單例模式單例模式
- 單例模式的各種實現方式(Java)單例模式Java
- 單例模式(下)---聊一聊單例模式的幾種寫法單例模式
- 單例模式(下) - 聊一聊單例模式的幾種寫法單例模式
- 單例模式(下) – 聊一聊單例模式的幾種寫法單例模式
- 設計模式總結 —— 單例設計模式設計模式單例
- 單例模式實現對比單例模式
- 單例模式c++實現單例模式C++
- Python中實現單例模式Python單例模式
- 單例模式你會幾種寫法?單例模式
- 單例模式個人整理單例模式
- 單例模式解析單例模式
- python 單例模式Python單例模式
- java 單例模式Java單例模式
- 單例模式(Singleton)單例模式
- php單例模式PHP單例模式
- 單例模式(3)單例模式
- Java單例模式Java單例模式
- 單例模式 singleton單例模式
- 單例模式(SingletonPattern)單例模式
- python單例模式Python單例模式
- 單例模式,真不簡單單例模式
- 利用static來實現單例模式單例模式
- 單例模式的各種實現單例模式
- 【php實現設計模式】之單例模式PHP設計模式單例
- 用Python實現設計模式——單例模式Python設計模式單例
- python如何實現單例模式?常用方法彙總!Python單例模式