手撕设计模式之「单例模式」(详细解析)
前言
單例模式主要用來(lái)保證系統(tǒng)中某個(gè)類(lèi)的實(shí)例對(duì)象的唯一性,是最簡(jiǎn)單的一種設(shè)計(jì)模式,而且在面試中也經(jīng)常會(huì)被問(wèn)到,是非常值得我們?nèi)W(xué)習(xí)的。如果你們面試遇到了哪些設(shè)計(jì)模式的考察,也歡迎留言,我會(huì)及時(shí)發(fā)新的博文。
文章目錄
- 前言
- 1. 模式定義
- 2. 模式實(shí)現(xiàn)
- 3. 單例模式的拓展
- 3.1 懶漢式單例
- 3.2 餓漢式單例
- 4. 多線程環(huán)境中的單例模式
- 4.1 延時(shí)加載
- 4.2 雙重校驗(yàn)鎖
- 4.3 靜態(tài)內(nèi)部類(lèi)
- 參考資料
1. 模式定義
單例模式(Singleton Pattern):確保某一個(gè)類(lèi)只有一個(gè)實(shí)例,而且自行實(shí)例化并像整個(gè)系統(tǒng)提供這個(gè)實(shí)例,這個(gè)類(lèi)稱(chēng)為單例類(lèi),它會(huì)提供全局訪問(wèn)的方法。單例模式是對(duì)象創(chuàng)建型模式的一種。
單例模式的三個(gè)要點(diǎn):
- 某個(gè)類(lèi)只能有一個(gè)實(shí)例
- 必須自行創(chuàng)建這個(gè)實(shí)例
- 必須自行向整個(gè)系統(tǒng)提供這個(gè)實(shí)例
2. 模式實(shí)現(xiàn)
要想實(shí)現(xiàn)單例模式,我們可以從它的三個(gè)要點(diǎn)入手:
首先是單例的唯一性,既然單例是唯一的,我們毫無(wú)疑問(wèn)應(yīng)該給他加上 static 關(guān)鍵字,又因?yàn)檫@個(gè)對(duì)象不應(yīng)該直接暴露,所以還要加上 private 進(jìn)行訪問(wèn)限定。
private static Singleton instince = null;接著,就是這個(gè)對(duì)象需要由單例類(lèi)自行創(chuàng)建,這時(shí)我們就應(yīng)該屏蔽外界訪問(wèn)該類(lèi)初始化方法的接口,也就是用 private 修飾構(gòu)造函數(shù)。
private Singleton() { }最后一點(diǎn)就比較容易實(shí)現(xiàn)了,給公有工廠方法 static 關(guān)鍵字,即可實(shí)現(xiàn)向整個(gè)系統(tǒng)提供這個(gè)實(shí)例。
public static Singleton getInstance() {if (instance == null) {instince = new Singleton();}return instance; }將以上代碼整合一下,就可以得到如下的單例模式的代碼實(shí)現(xiàn)模板:
public class Singleton {//靜態(tài)私有成員變量,保證實(shí)例的唯一性private static Singleton instince = null;//私有構(gòu)造函數(shù),保證實(shí)例由類(lèi)自行創(chuàng)建private Singleton() {}// 靜態(tài)公有工廠方法,向系統(tǒng)提供這個(gè)唯一實(shí)例public static Singleton getInstance() {if (instance == null) {instince = new Singleton();}return instance;} }下面是針對(duì)上面單例模式實(shí)現(xiàn)模板的客戶端測(cè)試代碼:
public Client {public static void main(String []args) {Singleton s1 = Singleton.getInstance();Singleton s2 = Singleton.getInstance();System.out.println(s1 == s2); //true} }3. 單例模式的拓展
單例模式可以分為懶漢式單例和餓漢式單例,它們的區(qū)別主要在單例類(lèi)對(duì)象的初始化時(shí)間上。下面我們來(lái)詳細(xì)講解:
3.1 懶漢式單例
懶漢式單例模式的結(jié)構(gòu)圖如下圖所示:
在模式圖中,我們可以看到這實(shí)例對(duì)象在創(chuàng)建類(lèi)的時(shí)候并沒(méi)有初始化,而是等到有人調(diào)用getInstance()方法獲取實(shí)例的時(shí)候才進(jìn)行實(shí)例對(duì)象的初始化,這也恰恰體現(xiàn)了懶漢式的這種“懶惰行為”。
下面是以“身份證號(hào)碼類(lèi)”作為例子,編寫(xiě)的懶漢式單例代碼。
public class IdentityCardNo {private String no;//某個(gè)類(lèi)只能有一個(gè)實(shí)例private static IdentityCardNo instince = null;//必須自行創(chuàng)建這個(gè)實(shí)例private IdentityCardNo() {}//必須自行向整個(gè)系統(tǒng)提供這個(gè)實(shí)例public static IdentityCardNo getInstance() {if (instince == null) {System.out.println("第一次辦理身份證,分配新號(hào)碼");instince = new IdentityCardNo();instince.setIdentityCardNo("400000199710301111");} else {System.out.println("重復(fù)辦理身份證,獲取舊號(hào)碼");}return instince;}public String getIdentityCardNo() {return no;}public void setIdentityCardNo(String no) {this.no = no;} }可以仿造上面的方法,寫(xiě)一個(gè)Client類(lèi)來(lái)進(jìn)行檢驗(yàn)。
3.2 餓漢式單例
餓漢式單例模式的結(jié)構(gòu)圖如下圖所示:
由于餓漢式單例的“饑餓”特性,使得它在類(lèi)加載階段就對(duì)單例類(lèi)對(duì)象進(jìn)行了初始化,從上面的結(jié)構(gòu)圖也可以看出這一點(diǎn)。
下面代碼是上面那個(gè)例子的餓漢式實(shí)現(xiàn):
public class IdentityCardNo {private String no;//某個(gè)類(lèi)只能有一個(gè)實(shí)例private static IdentityCardNo instince = new IdentityCardNo();//必須自行創(chuàng)建這個(gè)實(shí)例private IdentityCardNo() {System.out.println("第一次辦理身份證,分配新號(hào)碼");}//必須自行向整個(gè)系統(tǒng)提供這個(gè)實(shí)例public static IdentityCardNo getInstance() {instince.setIdentityCardNo("400000199710301111");return instince;}public String getIdentityCardNo() {return no;}public void setIdentityCardNo(String no) {this.no = no;} }4. 多線程環(huán)境中的單例模式
上面提到的懶漢式單例是線程不安全的,在多線程的環(huán)境下,對(duì)象的唯一性得不到保障。于是就有了下面幾種線程安全的單例模式實(shí)現(xiàn)方法,其中第1,2種方法是對(duì)懶漢式的改進(jìn),第 3 中是對(duì)餓漢式的改進(jìn)。
4.1 延時(shí)加載
我們可以用synchronized關(guān)鍵字修飾getInstance()方法,利用延時(shí)加載的方式保證在多線程環(huán)境下對(duì)象的唯一性。但是這種方法在容易造成線程擁塞,效率不高。
實(shí)現(xiàn)代碼如下:
public class Singleton {private static Singleton instince = null;private Singleton() {}public static synchronized Singleton getInstance() {if (instance == null) {instince = new Singleton();}return instance;} }4.2 雙重校驗(yàn)鎖
這種方式采用雙鎖機(jī)制,線程安全且在多線程情況下能保持高性能,是比較推薦使用的方法。
實(shí)現(xiàn)代碼如下:
public class Singleton {private static Singleton instince = null;private Singleton() {}public static Singleton getInstance() {if (instance == null) {//只將synchronized關(guān)鍵字用在了初始化模塊synchronized (Singleton.class) {if (install == null) {instince = new Singleton();}}}return instance;} }4.3 靜態(tài)內(nèi)部類(lèi)
這種方式通過(guò)給對(duì)象加上final關(guān)鍵字修飾,能達(dá)到雙檢鎖方式一樣的功效,但實(shí)現(xiàn)更簡(jiǎn)單。對(duì)靜態(tài)域使用延遲初始化,應(yīng)使用這種方式而不是雙檢鎖方式。這種方式只適用于靜態(tài)域的情況,雙檢鎖方式可在實(shí)例域需要延遲初始化時(shí)使用。
實(shí)現(xiàn)代碼如下:
public class Singleton {private static final Singleton instince = new Singleton();private Singleton() {}public static Singleton getInstance() {return Singleton.instance;} }參考資料
總結(jié)
以上是生活随笔為你收集整理的手撕设计模式之「单例模式」(详细解析)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 剑指Offer #14 链表中倒数第k个
- 下一篇: XML简介及基本语法