所有知識體系文章,GitHub已收錄,歡迎Star!再次感謝,願你早日進入大廠! GitHub地址: https://github.com/Ziphtracks/JavaLearningmanual 01設計模式之單例設計模式 一、什麼是單例設計模式? 單例模式(Singleton Pattern) ...
所有知識體系文章,GitHub已收錄,歡迎Star!再次感謝,願你早日進入大廠!
GitHub地址: https://github.com/Ziphtracks/JavaLearningmanual
01設計模式之單例設計模式
一、什麼是單例設計模式?
單例模式(Singleton Pattern)是 Java 中最簡單的設計模式之一。這種類型的設計模式屬於創建型模式,它提供了一種創建對象的最佳方式。
這種模式涉及到一個單一的類,該類負責創建自己的對象,同時確保只有單個對象被創建。這個類提供了一種訪問其唯一的對象的方式,可以直接訪問,不需要實例化該類的對象。
註意:
- 單例類只能有一個實例。
- 單例類必須自己創建自己的唯一實例。
- 單例類必須給所有其他對象提供這一實例。
二、單例設計模式的優缺點
優點:
- 在記憶體中只有一個實例對象,減少記憶體開銷。解決了頻繁創建和銷毀記憶體實例對象的問題。
- 避免過多的資源占用。比如:寫文件操作。
缺點:
- 沒有介面,不能繼承,與單一職責原則衝突,一個類應該只關心內部邏輯,而不關心外面怎麼樣來實例化。
三、單例設計模式的使用
當想要控制實例數目,節省系統資源的時候。並且在一個全局內,解決記憶體中頻繁創建和銷毀實例對象問題。
四、單例設計模式分類
單例設計模式的原則是創建唯一實例,但是創建唯一實例的方法有很多,也由此生成了許多種類的單例設計模式。它們分別是:普通懶漢式單例模式、同步鎖懶漢式單例模式、同步代碼塊懶漢式案例模式、餓漢式單例模式、雙重校驗鎖(雙檢鎖)單例模式、靜態內部類(登記式)單例模式和枚舉單例模式
五、單例模式思想的傳遞過程
問題: 如果我們要有寫單例設計模式的思想,該如何實現單例設計模式呢?怎樣才能實現全局內只創建一個實例化對象並使用呢?而且在使用過程中會不會出現其他問題呢?帶著疑問先把最基礎的單例設計模式寫出來!
首先,先創建一個類,類名為Singleton。然後去創建一個對象如下:
class Singleton {
Singleton instance = new Singleton();
}
看到這裡,我們就發現這是一個普通的類,創建一個普通類的實例對象。那麼我們如果實現單個實例對象的話,就不能讓外界隨便來訪問創建該對象。所以我們就想到了構造方法,大家知道如果類中寫任何構造方法的話,它會隱式的存在一個公共的無參構造。這時候聰明的小伙伴想到了私有該構造器,不讓外界隨便創建對象。代碼如下:
class Singleton {
//在類的內部創建一個類的實例對象
private Singleton instance = new Singleton();
//私有化構造器,使得在類的外部不能夠調用此構造器,隨意創建實例對象
private Singleton() {}
}
那麼下一步呢?我們如何為外界提供該類內部的實例對象呢?有的小伙伴在創建類的實例對象的同時使用了修飾符static。我真的說著很聰明。這樣被static修飾了之後就可以通過類名來句點出來對象使用了?那麼問題來了。如果出現以下狀況怎麼辦呢?看以下操作!
class Singleton {
//在類的內部創建一個類的實例對象,該靜態修飾的對象隨著類載入只創建一次實例
private static Singleton instance = new Singleton();
//私有化構造器,使得在類的外部不能夠調用此構造器,隨意創建實例對象
private Singleton() {}
}
class Test {
//分別創建了兩個對象為s1和s2,並且兩個對象是使用了同一個實例對象
Singleton s1 = Singleton.instance;
Singleton s2 = Singleton.instance;
//不信的話,你可以比較一下兩個對象的地址
System.out.println(s1 == s2);//結果true,證明是同一個對象
}
結果很好,那麼我又來問問題了。如果創建了兩個對象,這時原來的實例對象改變了,會有什麼結果呢?那不就創建的兩個實例對象不是同一個了嘛。對,很對。不是同一個對象了。來再繼續看以下場景!
class Test {
//又分別創建了兩個對象為s3和s4,這時我將原實例對象改變一下,把它置為空,會有什麼結果呢?
Singleton s3 = Singleton.instance;
//把原實例對象置為空
Singleton.instance = null;
Singleton s4 = Singleton.instance;
//比較兩個對象的地址
System.out.println(s1 == s2);//結果false,證明不是使用的同一個實例對象
}
因為上面的場景外界可以改變原來的實例對象,而造成創建實例不一致,那麼我們就想辦法限制外界更改實例。那肯定有小伙伴想到使用get方法,為類提供一個get方法,將創建好的實例對象提供給外界使用就ok了。那麼get方法是外界隨便就可以使用的嗎,常規來說,get方法是通過創建實例對象後句點出來的,那外界創建不了實例對象我們怎麼辦?別忘了我們有static修飾符,加了它不就能用類名句點出來嘛。代碼如下,此代碼也是最終版了!
//餓漢式單例模式
class Singleton {
//1.在類的內部創建一個類的實例,該靜態修飾的對象隨著類載入只創建一次實例
private static final Singleton instance = new Singleton();
//2.私有化構造器,使得在類的外部不能夠調用此構造器
private Singleton() {
}
//3.私有化此對象,通過公共的方法來調用
//4.公共的方法,只能通過類來調用,因為設置為static的,同時類的實例也必須為static聲明的
public static Singleton getInstance() {
return instance;
}
}
細心的小伙伴,會發現我在創建實例的時候不單單加了static修飾,而且還使用final修飾。這是為什麼呢?其實加final是為了該對象不被改變,是代碼更見健壯而已!
六、懶載入(Lazy Load)
懶載入(Lazy Load),這裡的懶載入指的是在使用實例對象的時候才會去創建實例對象。這就避免了資源的浪費和記憶體的占用問題。其實懶載入無非就是在空間換時間與時間換空間中的取捨!
再一次提問: 而且該實現還遺留了一個問題那就是,假如在此類中寫的代碼。我們不管用不用該實例對象,它類載入的時候就自動創建一個對象。會造成資源的浪費和記憶體的占用。雖然占用的不多,但是也是一種漏洞,懂吧!
那我我們考慮在單例模式思想傳遞過程中的終極版(此終極版就是餓漢式單例模式),它不支持懶載入。那怎樣才能支持懶載入呢?那我們就需要控制創建對象不在類載入的時候創建,而是在get方法中創建實例對象為外界提供。先看代碼吧,以下方法實現單例模式就支持懶載入了!
//普通懶漢式單例模式(線程不安全)
class Singleton {
//創建實例對象
private static Singleton instance = null;
//私有化構造器
private Singleton() {
}
//提供static修飾的get方法,以供外界創建實例對象
public static Singleton getInstance() {
//判斷實例對象是否為空,為空則創建實例對象並返回
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
七、單例設計模式的線程安全問題
問題: 在單例設計模式中什麼是線程不安全?
單例模式中,只會創建一個實例對象,也就是外界使用的實例對象是同一個對象,當然既然是同一個他們的地址都是相同的!所謂單例設計模式中的線程不安全,就是存在可以創建多個該實例對象的現象!
問題: 普通懶漢式單例模式是怎樣個線程不安全呢?如果將其改裝為線程安全的呢?
單例設計模式的線程安全問題,繼懶載入問題分析後,普通的懶漢式單例模式會存線上程安全問題。
在單例設計模式創建實例對象是一個原子操作!它的線程不安全,可以解釋為多個線程在併發訪問創建此單例對象時,同時在判空環節搶到了CUP的時間片,創建了兩個或多個該實例對象。破壞了單例設計模式單實例對象原則!
線程安全的單例模式有很多,比如介紹思想傳遞過程時的那個餓漢式單例模式,它天生就是線程安全的,你好好琢磨一下餓漢式,我不可能有創建多個實例的情況!
線程安全的單例模式,在第八章的分類剖析中,我會一一列舉,並將所有單例模式作對於寫出他們的特點、優缺點等等!
改裝普通懶漢式單例模式並解決線程安全問題
如果想要改裝普通懶漢式單例模式,我們就必須使用到同步鎖(synchronized)了!如下兩種操作可以解決普通懶漢式線程安全問題!代碼如下:
1.為原子操作方法加同步鎖
//同步鎖懶漢式單例模式(線程安全)
class Singleton {
//創建實例對象
private static Singleton instance = null;
//私有化構造器
private Singleton() {
}
//加同步鎖並被static修飾的get方法
public synchronized static Singleton getInstance() {
//具體來說以下是原子操作
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
2.為該實例對象加同步代碼塊
註意: 在使用同步代碼塊的時候,括弧內不能是this。因為我們使用static修飾創建對象,同步的對象不可能同步外界通過static句點出來的對象的,因為此操作並不合理。所以,此處寫了this會飄紅報錯!
//同步鎖懶漢式單例模式(線程安全)
class Singleton {
//創建實例對象
private static Singleton instance = null;
//私有化構造器
private Singleton() {
}
//加同步鎖並被static修飾的get方法
public static Singleton getInstance() {
//此處鎖的是實例對象
synchronized(Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
}