欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

一文搞懂設(shè)計模式中的單例模式

 更新時間:2023年08月04日 10:36:16   作者:wh柒八九  
這篇文章主要介紹了一文搞懂設(shè)計模式中的單例模式,單例模式是最簡單的設(shè)計模式之一,屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對象的方式,確保只有單個對象被創(chuàng)建,需要的朋友可以參考下

單例模式

保證一個類僅有一個實(shí)例,并提供一個訪問它的全局訪問點(diǎn)。

單例模式的意義

通常面試官會很籠統(tǒng)的問你,什么是單例模式?單例模式用來解決了什么痛點(diǎn)?沒有單例模式我們會怎么辦?單例模式他有什么缺點(diǎn)嗎?

單例模式是最簡單的設(shè)計模式之一,屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對象的方式,確保只有單個對象被創(chuàng)建。

這個設(shè)計模式主要目的是想在整個系統(tǒng)中只能出現(xiàn)類的一個實(shí)例,即一個類只有一個對象。

單例模式的解決的痛點(diǎn)就是節(jié)約資源,節(jié)省時間從兩個方面看:

  • 由于頻繁使用的對象,可以省略創(chuàng)建對象所花費(fèi)的時間,這對于那些重量級的對象而言,是很重要的.
  • 因?yàn)椴恍枰l繁創(chuàng)建對象,我們的GC壓力也減輕了,而在GC中會有STW(stop the world),從這一方面也節(jié)約了GC的時間

單例模式的缺點(diǎn):

簡單的單例模式設(shè)計開發(fā)都比較簡單,但是復(fù)雜的單例模式需要考慮線程安全等并發(fā)問題,引入了部分復(fù)雜度。

擴(kuò)展:從你的回答中能進(jìn)行哪些擴(kuò)展呢?我們談到了GC,有可能這時候就會問你GC,STW等知識。談缺點(diǎn)的時候談到了復(fù)雜的單例模式,

這個時候可能會問你讓你設(shè)計一個優(yōu)秀的單例模式你會怎么設(shè)計,會怎么實(shí)現(xiàn)?

單例模式的設(shè)計

通常這里面試官會問你單例模式怎么設(shè)計,需要看重哪些方面?一般來說單例模式有哪些實(shí)現(xiàn)方式?

通常單例模式在Java語言中,有兩種構(gòu)建方式

  • 餓漢方式。指全局的單例實(shí)例在類裝載時構(gòu)建
  • 懶漢方式。指全局的單例實(shí)例在第一次被使用時構(gòu)建。

不管是那種創(chuàng)建方式,它們通常都存在下面幾點(diǎn)相似處

單例類必須要有一個 private 訪問級別的構(gòu)造函數(shù),只有這樣,才能確保單例不會在系統(tǒng)中的其他代碼內(nèi)被實(shí)例化; instance 成員變量和 uniqueInstance 方法必須是 static 的。

設(shè)計單例模式的時候一般需要考慮幾種因素:-線程安全 -延遲加載 -代碼安全:如防止序列化攻擊,防止反射攻擊(防止反射進(jìn)行私有方法調(diào)用) -性能因素

一般來說,我們?nèi)ゾW(wǎng)上百度去搜大概有7,8種實(shí)現(xiàn),下面列舉一下需要重點(diǎn)知道的餓漢,懶漢(線程安全,線程非安全),雙重檢查(DCL)(重點(diǎn)),內(nèi)部類,以及枚舉(重點(diǎn)),

下面比對下各個實(shí)現(xiàn):

名稱線程安全并發(fā)性能好可以延遲加載序列化/反序列化安全能抵御反射攻擊
餓漢式YYNNN
懶漢式(不加鎖)NYYNN
懶漢式(加鎖)YNYNN
DCLYYYNN
靜態(tài)內(nèi)部類YYYNN
枚舉YYNYY

擴(kuò)展:

我們上面說到了各個模式的實(shí)現(xiàn),這個時候很有可能會叫你手寫各個模式的代碼。當(dāng)然也有可能會問你線程安全,代碼安全等知識。

餓漢模式

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

所謂 “餓漢方式” 就是說JVM在加載這個類時就馬上創(chuàng)建此唯一的單例實(shí)例,不管你用不用,先創(chuàng)建了再說

如果一直沒有被使用,便浪費(fèi)了空間,典型的空間換時間,每次調(diào)用的時候,就不需要再判斷,節(jié)省了運(yùn)行時間。

餓漢模式代碼比較簡單,對象在類中被定義為private static,通過getInstance(),通過java的classLoader機(jī)制保證了單例對象唯一。

擴(kuò)展:

有可能會問instance什么時候被初始化?  

Singleton類被加載的時候就會被初始化,java虛擬機(jī)規(guī)范雖然沒有強(qiáng)制性約束在什么時候開始類加載過程,但是對于類的初始化,虛擬機(jī)規(guī)范則嚴(yán)格規(guī)定了有且只有四種情況必須立即對類進(jìn)行初始化,遇到new、getStatic、putStatic或invokeStatic這4條字節(jié)碼指令時,如果類沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化。

生成這4條指令最常見的java代碼場景是:

1)使用new關(guān)鍵字實(shí)例化對象

2)讀取一個類的靜態(tài)字段(被final修飾、已在編譯期把結(jié)果放在常量池的靜態(tài)字段除外)

3)設(shè)置一個類的靜態(tài)字段(被final修飾、已在編譯期把結(jié)果放在常量池的靜態(tài)字段除外)

4)調(diào)用一個類的靜態(tài)方法  

class的生命周期?  

class的生命周期一般來說會經(jīng)歷加載、連接、初始化、使用、和卸載五個階段  

class的加載機(jī)制  

這里可以聊下classloader的雙親委派模型。

懶漢式(非線程安全和synchronized關(guān)鍵字線程安全版本 )

public class Singleton {  
      private static Singleton uniqueInstance;  
      private Singleton (){
      }   
      //沒有加入synchronized關(guān)鍵字的版本是線程不安全的
      public static Singleton getInstance() {
          //判斷當(dāng)前單例是否已經(jīng)存在,若存在則返回,不存在則再建立單例
	      if (uniqueInstance == null) {  
	          uniqueInstance = new Singleton();  
	      }  
	      return uniqueInstance;  
      }  
 }

所謂 “餓漢方式” 就是說單例實(shí)例在第一次被使用時構(gòu)建,而不是在JVM在加載這個類時就馬上創(chuàng)建此唯一的單例實(shí)例。

但是上面這種方式很明顯是線程不安全的,如果多個線程同時訪問getInstance()方法時就會出現(xiàn)問題。

如果想要保證線程安全,一種比較常見的方式就是在getInstance() 方法前加上synchronized關(guān)鍵字,如下:

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

我們知道synchronized關(guān)鍵字偏重量級鎖。

雖然在JavaSE1.6之后synchronized關(guān)鍵字進(jìn)行了主要包括:

為了減少獲得鎖和釋放鎖帶來的性能消耗而引入的偏向鎖和輕量級鎖以及其它各種優(yōu)化之后執(zhí)行效率有了顯著提升。

但是在程序中每次使用getInstance() 都要經(jīng)過synchronized加鎖這一層,這難免會增加getInstance()的方法的時間消費(fèi),而且還可能會發(fā)生阻塞。我們下面介紹到的 雙重檢查加鎖版本 就是為了解決這個問題而存在的。

懶漢式(雙重檢查加鎖版本)

利用雙重檢查加鎖(double-checked locking),首先檢查是否實(shí)例已經(jīng)創(chuàng)建,如果尚未創(chuàng)建,“才”進(jìn)行同步。這樣以來,只有一次同步,這正是我們想要的效果。

public class Singleton {
    //volatile保證,當(dāng)uniqueInstance變量被初始化成Singleton實(shí)例時,多個線程可以正確處理uniqueInstance變量
    private volatile static Singleton uniqueInstance;
    private Singleton() {
    }
    public static Singleton getInstance() {
       //檢查實(shí)例,如果不存在,就進(jìn)入同步代碼塊
        if (uniqueInstance == null) {
            //只有第一次才徹底執(zhí)行這里的代碼
            synchronized(Singleton.class) {
               //進(jìn)入同步代碼塊后,再檢查一次,如果仍是null,才創(chuàng)建實(shí)例
                if (uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

synchronized同步塊里面能夠保證只創(chuàng)建一個對象。但是通過在synchronized的外面增加一層判斷,就可以在對象一經(jīng)創(chuàng)建以后,不再進(jìn)入synchronized同步塊。這種方案不僅減小了鎖的粒度,保證了線程安全,性能方面也得到了大幅提升。

同時這里要注意一定要說volatile,這個很關(guān)鍵,volatile一般用于多線程的可見性,但是這里是用來防止指令重排序的。

很明顯,這種方式相比于使用synchronized關(guān)鍵字的方法,可以大大減少getInstance() 的時間消費(fèi)。

懶漢式(登記式/靜態(tài)內(nèi)部類方式)

靜態(tài)內(nèi)部實(shí)現(xiàn)的單例是懶加載的且線程安全。

只有通過顯式調(diào)用 getInstance 方法時,才會顯式裝載 SingletonHolder 類,從而實(shí)例化 instance(只有第一次使用這個單例的實(shí)例的時候才加載,同時不會有線程安全問題)。

public class Singleton {  
    private static class SingletonHolder {  
    private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
    return SingletonHolder.INSTANCE;  
    }  
}  

餓漢式(枚舉方式)

這種實(shí)現(xiàn)方式還沒有被廣泛采用,但這是實(shí)現(xiàn)單例模式的最佳方法。 它更簡潔,自動支持序列化機(jī)制,絕對防止多次實(shí)例化 (如果單例類實(shí)現(xiàn)了Serializable接口,默認(rèn)情況下每次反序列化總會創(chuàng)建一個新的實(shí)例對象,同時這種方式也是《Effective Java 》以及《Java與模式》的作者推薦的方式。

public enum Singleton {
	 //定義一個枚舉的元素,它就是 Singleton 的一個實(shí)例
    INSTANCE;  
    public void doSomeThing() {  
	     System.out.println("枚舉方法實(shí)現(xiàn)單例");
    }  
}

使用方法:

public class ESTest {
	public static void main(String[] args) {
		Singleton singleton = Singleton.INSTANCE;
		singleton.doSomeThing();//output:枚舉方法實(shí)現(xiàn)單例
	}
}

擴(kuò)展閱讀:

《Effective Java 中文版 第二版》

這種方法在功能上與公有域方法相近,但是它更加簡潔,無償提供了序列化機(jī)制,絕對防止多次實(shí)例化,即使是在面對復(fù)雜序列化或者反射攻擊的時候。雖然這種方法還沒有廣泛采用,但是單元素的枚舉類型已經(jīng)成為實(shí)現(xiàn)Singleton的最佳方法。 —-《Effective Java 中文版 第二版》

《Java與模式》

《Java與模式》中,作者這樣寫道,使用枚舉來實(shí)現(xiàn)單實(shí)例控制會更加簡潔,而且無償?shù)靥峁┝诵蛄谢瘷C(jī)制,并由JVM從根本上提供保障,絕對防止多次實(shí)例化,是更簡潔、高效、安全的實(shí)現(xiàn)單例的方式。

本文小結(jié)

單例模式雖然看起來簡單,但是設(shè)計的Java基礎(chǔ)知識非常多,如static修飾符、synchronized修飾符、volatile修飾符、enum等。

這里的每一個知識點(diǎn)都可以變成面試官下手的考點(diǎn),而單例只是作為一個引子,考到最后看你到底掌握了多少??茨愕膹V度和深度到底是怎么樣的。

到此這篇關(guān)于一文搞懂設(shè)計模式中的單例模式的文章就介紹到這了,更多相關(guān)設(shè)計模式中的單例模式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論