JAVA設(shè)計模式系列——單例模式
設(shè)計模式,是一套被反復(fù)使用、多數(shù)人知曉的、經(jīng)過分類編目的、代碼設(shè)計經(jīng)驗的總結(jié)。它描述了在軟件設(shè)計過程中的一些不斷重復(fù)發(fā)生的問題,以及該問題的解決方案。
也就是說,它是解決特定問題的一系列套路,是前輩們的代碼設(shè)計經(jīng)驗的總結(jié),具有一定的普遍性,可以反復(fù)使用。
其目的是為了提高代碼的可重用性、代碼的可讀性和代碼的可靠性。
那么我們今天先來看看在我們現(xiàn)學(xué)習(xí)階段使用特別多的一種設(shè)計模式-單例模式。
首先我們看看單例模式在實際生活中的使用場景:
比如:
公司 CEO、部門經(jīng)理等都屬于單例模型
J2EE 標(biāo)準(zhǔn)中的 ServletContext 和 ServletContextConfig
Spring
數(shù)據(jù)庫中的連接池等也都是單例模式
還比如 Windows 的回收站
操作系統(tǒng)中的文件系統(tǒng)
多線程中的線程池
顯卡的驅(qū)動程序?qū)ο?/p>
打印機的后臺處理服務(wù)
應(yīng)用程序的日志對象
數(shù)據(jù)庫的連接池
網(wǎng)站的計數(shù)器
Web 應(yīng)用的配置對象
應(yīng)用程序中的對話框
系統(tǒng)中的緩存等常常被設(shè)計成單例。
那么這些場合為什么要設(shè)計成單例模式呢?單例模式的實現(xiàn)方式有哪些?單例模式的好處和弊端有哪些呢?
帶著這些問題我們一起來詳細(xì)看看單例模式

1.什么是單例模式?
單例(Singleton)模式的定義:指一個類只有一個實例,且該類能自行創(chuàng)建這個實例的一種模式。
例如:Windows 中只能打開一個任務(wù)管理器,這樣可以避免因打開多個任務(wù)管理器窗口而造成內(nèi)存資源的浪費,或出現(xiàn)各個窗口顯示內(nèi)容的不一致等錯誤。
2.單例模式的實現(xiàn)方式有哪些?
常見的實現(xiàn)方式有:懶漢模式、饑漢模式、雙重校驗鎖、靜態(tài)內(nèi)部類、枚舉等方式實現(xiàn),那我們我們緊接著就具體的一個一個的來看看他們的實現(xiàn)
懶漢模式:
這段代碼里,我們沒有考慮線程安全,所以可能還是會參數(shù)多個實例,所以我們需要對線程安全問題進行解決,從而得出懶漢模式的優(yōu)化版
通過關(guān)鍵字synchronized
用于確保getInstance
方法線程安全,但是這種方式弊端特別大,因為所有線程到達(dá)該方法以后需要進行排隊等候,所以對性能的損耗非常大,該處理解決了線程安全安全問題,并沒有解決效率問題,如果要既要解決線程安全問題又要解決效率問題則需要我們后面的雙重校驗鎖方式。
懶漢模式將實例化的時機放到了需要使用的時候(餓漢是類加載了就有實例),也就是“延遲加載”,相比餓漢,能避免了在加載的時候?qū)嵗锌赡苡貌坏降膶嵗?,但是問題也很明顯,我們要花精力去解決線程安全的問題。
餓漢模式:
餓漢模式相比懶漢模式,在類加載的時候就已經(jīng)存在一個實例,舉個例子,比如數(shù)據(jù)庫連接吧,懶漢就是第一次訪問數(shù)據(jù)庫的時候我才去創(chuàng)建一個連接,而餓漢呢,是你程序啟動了,類加載好了的時候,我已經(jīng)有個連接了,你用不用不一定了,所以餓漢的缺點也就出來了:可能會產(chǎn)生很多無用的實例。
那么加載時機的問題我們已經(jīng)說過了,接下來就是線程安全了,代碼里我們并沒有看見synchronized
關(guān)鍵字,那么這種方式是如何確保線程安全的呢,這個就是JVM類加載的特性了,JVM在加載類的時候,是單線程的,所以可以保證只存在單一的實例
雙重校驗鎖:
首先要說明的是,雙重檢驗鎖也是一種延遲加載,并且較好的解決了在確保線程安全的時候效率低下的問題,對比一下最原始的那種線程安全的方法(就是懶漢模式的第二種代碼),那種方法將整個getInstance
方法鎖住,那么每次調(diào)用那個方法都要獲得鎖,釋放鎖,等待等等...而雙重校驗鎖鎖住了部分的代碼。進入方法如果檢查為空才進入同步代碼塊,這樣很明顯效率高了很多。
靜態(tài)內(nèi)部類:
懶漢模式需要考慮線程安全,所以我們多寫了好多的代碼,餓漢模式利用了類加載的特性為我們省去了線程安全的考慮,那么,既能享受類加載確保線程安全帶來的便利,又能延遲加載的方式,就是靜態(tài)內(nèi)部類。
Java靜態(tài)內(nèi)部類的特性是:加載的時候不會加載內(nèi)部靜態(tài)類,使用的時候才會進行加載。而使用到的時候類加載又是線程安全的,這就完美的達(dá)到了我們的預(yù)期效果。
枚舉:
JDK1.5提供了一個新的數(shù)據(jù)類型:枚舉。
枚舉的出現(xiàn)提供了一個較為優(yōu)雅的方式取代以前大量的static final類型的變量。而這里,我們也利用枚舉的特性,實現(xiàn)了單例模式,外部調(diào)用由原來的Singleton.getInstance
變成了Singleton.INSTANCE
了。
以上幾種單例模式的實現(xiàn)各自有各自的優(yōu)勢,所以我們在實際使用中,需要根據(jù)自己的需求進行選擇,而通過上述實現(xiàn)方式我們可以可以總結(jié)一下單例模式的特點:
1).單例模式只有一個實例對象;
2).該單例對象必須由單例類創(chuàng)建;
3).單例類對外提供一個訪問該單例的全局訪問點。
3.單例模式的優(yōu)缺點:
以上我們具體的對比了幾種單例模式的實現(xiàn)方式,那么單例模式有哪些優(yōu)缺點呢?
優(yōu)點:
單例模式可以保證內(nèi)存里只有一個實例,減少了內(nèi)存的開銷。
可以避免對資源的多重占用。
單例模式設(shè)置全局訪問點,可以優(yōu)化和共享資源的訪問。
缺點:
單例模式一般沒有接口,擴展困難。如果要擴展,則除了修改原來的代碼,沒有第二種途徑,違背開閉原則。
在并發(fā)測試中,單例模式不利于代碼調(diào)試。在調(diào)試過程中,如果單例中的代碼沒有執(zhí)行完,也不能模擬生成一個新的對象。
單例模式的功能代碼通常寫在一個類中,如果功能設(shè)計不合理,則很容易違背單一職責(zé)原則。
4.單例模式的 應(yīng)用場景:
對于 Java 來說,單例模式可以保證在一個 JVM 中只存在單一實例。
單例模式的應(yīng)用場景主要有以下幾個方面:
需要頻繁創(chuàng)建的一些類,使用單例可以降低系統(tǒng)的內(nèi)存壓力,減少 GC。
某類只要求生成一個對象的時候,如一個班中的班長、每個人的身份證號等。
某些類創(chuàng)建實例時占用資源較多,或?qū)嵗臅r較長,且經(jīng)常使用。
某類需要頻繁實例化,而創(chuàng)建的對象又頻繁被銷毀的時候,如多線程的線程池、網(wǎng)絡(luò)連接池等。
頻繁訪問數(shù)據(jù)庫或文件的對象。
對于一些控制硬件級別的操作,或者從系統(tǒng)上來講應(yīng)當(dāng)是單一控制邏輯的操作,如果有多個實例,則系統(tǒng)會完全亂套。
當(dāng)對象需要被共享的場合。由于單例模式只允許創(chuàng)建一個對象,共享該對象可以節(jié)省內(nèi)存,并加快對象訪問速度。如 Web 中的配置對象、數(shù)據(jù)庫的連接池等。

5.總結(jié):
在單例模式各種設(shè)計的方法中,我們使用到了內(nèi)部靜態(tài)類的特性,使用了枚舉的特性,所以基礎(chǔ)非常重要,單例模式是設(shè)計模式之一,而設(shè)計模式其實是對語言特性不足的一面進一步的包裝。吸納基礎(chǔ),工作學(xué)習(xí)多加思考,設(shè)計模式也就自然而然的能夠理解。