概念: 單例模式:一個類中只有一個實例。 一個類有且僅有一個實例,並且提供了一個全局的訪問點。 使用該模式的起因: 當我們在瀏覽網站時,有些網站會顯示“當前線上人數”。通常,實現這個功能的辦法是將登陸的每一個IP存儲在一個記憶體、文件或者資料庫中,每多一個IP,就實現“+1”。一般就是用一個方法,比如 ...
概念:
單例模式:一個類中只有一個實例。
一個類有且僅有一個實例,並且提供了一個全局的訪問點。
使用該模式的起因:
當我們在瀏覽網站時,有些網站會顯示“當前線上人數”。通常,實現這個功能的辦法是將登陸的每一個IP存儲在一個記憶體、文件或者資料庫中,每多一個IP,就實現“+1”。一般就是用一個方法,比如add(),實現“+1”的功能,比如用“update”語句,先獲取資料庫中存儲的數據,再+1,更新資料庫中的數據,,然後保存;顯示在頁面時,再通過另外的方法獲取資料庫中的數據即可。但是,當多個用戶同時登陸時,如果每一個都要new一個對象,然後再通過“對象.方法名”調用執行add()方法,再將數據存儲到資料庫中,這樣就會導致多個用戶無法將實際的用戶數據準確的記錄到資料庫中。所以,把這個計數器設計為一個全局對象(所有人都使用這一個對象,而不是用一個,new一個),所有人都共用同一份數據,就可以避免類似的問題,這就是我們所說的單例模式的其中的一種應用。
同樣的,還有其他場景中,也會遇到相似的情景,使用到類似的思路。比如:
1.外部資源:每台電腦有若幹個印表機,但只能有一個PrinterSpooler,以避免兩個列印作業同時輸出到印表機。內部資源:大多數軟體都有一個(或多個)屬性文件存放系統配置,這樣的系統應該有一個對象管理這些屬性文件
2. Windows的Task Manager(任務管理器)就是很典型的單例模式(這個很熟悉吧),想想看,是不是呢,你能打開兩個windows task manager嗎? 不信你自己試試看哦~
3. windows的Recycle Bin(回收站)也是典型的單例應用。在整個系統運行過程中,回收站一直維護著僅有的一個實例。
4. 網站的計數器,一般也是採用單例模式實現,否則難以同步。
5. 應用程式的日誌應用,一般都何用單例模式實現,這一般是由於共用的日誌文件一直處於打開狀態,因為只能有一個實例去操作,否則內容不好追加。
6. Web應用的配置對象的讀取,一般也應用單例模式,這個是由於配置文件是共用的資源。
7. 資料庫連接池的設計一般也是採用單例模式,因為資料庫連接是一種資料庫資源。資料庫軟體系統中使用資料庫連接池,主要是節省打開或者關閉資料庫連接所引起的效率損耗,這種效率上的損耗還是非常昂貴的,因為何用單例模式來維護,就可以大大降低這種損耗。
8. 多線程的線程池的設計一般也是採用單例模式,這是由於線程池要方便對池中的線程進行控制。
9. 操作系統的文件系統,也是大的單例模式實現的具體例子,一個操作系統只能有一個文件系統。
10. HttpApplication 也是單位例的典型應用。熟悉ASP.Net(IIS)的整個請求生命周期的人應該知道HttpApplication也是單例模式,所有的HttpModule都共用一個HttpApplication實例。
總結起來,單例模式的一般應用場景為:
1.需要頻繁實例化然後銷毀的對象。
2.創建對象時耗時過多或者耗資源過多,但又經常用到的對象。
3.有狀態的工具類對象。
4.頻繁訪問資料庫或者文件的對象。
5.資源共用的情況下,避免由於資源操作時導致的性能或損耗等。如上述中的日誌文件、應用配置等。
6.控制資源的情況下,方便資源之間的互相通信。如線程池等。
特點:
1、單例類只能有一個實例;
2、單例類必須自己創建自己的唯一實例;
3、單例類必須給所有其他對象提供這一實例
單例模式要素:
1.私有構造方法
2.私有靜態引用指向自己實例
3.以自己實例為返回值的公有靜態方法
實現單例模式的三種方法:
1.餓漢式:單例實例在類裝載時就構建,急切初始化。(預先載入法)
/**
* 餓漢式(推薦)
*
*/
public class Test {
private Test() {
}
public static Test instance = new Test();
public Test getInstance() {
return instance;
}
}
優點
1.線程安全
2.在類載入的同時已經創建好一個靜態對象,調用時反應速度快
缺點
資源效率不高,可能getInstance()永遠不會執行到,但執行該類的其他靜態方法或者載入了該類(class.forName),那麼這個實例仍然初始化
2.懶漢式:單例實例在第一次被使用時構建,延遲初始化。
class Test {
private Test() {
}
public static Test instance = null;
public static Test getInstance() {
if (instance == null) {
//多個線程判斷instance都為null時,在執行new操作時多線程會出現重覆情況
instance = new Singleton2();
}
return instance;
}
}
優點
避免了餓漢式的那種在沒有用到的情況下創建事例,資源利用率高,不執行getInstance()就不會被實例,可以執行該類的其他靜態方法。
缺點
懶漢式在單個線程中沒有問題,但多個線程同事訪問的時候就可能同事創建多個實例,而且這多個實例不是同一個對象,雖然後面創建的實例會覆蓋先創建的實例,但是還是會存在拿到不同對象的情況。解決這個問題的辦法就是加鎖synchonized,第一次載入時不夠快,多線程使用不必要的同步開銷大。
3.雙重檢測
class Test {
private Test() {
}
public static Test instance = null;
public static Test getInstance() {
if (instance == null) {
synchronized (Test.class) {
if (instance == null) {
instance = new Test();
}
}
}
return instance;
}
}
優點
資源利用率高,不執行getInstance()就不被實例,可以執行該類其他靜態方法
缺點
第一次載入時反應不快,由於java記憶體模型一些原因偶爾失敗
4.靜態內部類
class Test {
private Test() {
}
private static class SingletonHelp {
static Test instance = new Test();
}
public static Test getInstance() {
return SingletonHelp.instance;
}
}
優點
資源利用率高,不執行getInstance()不被實例,可以執行該類其他靜態方法
缺點
第一次載入時反應不夠快
總結:
一般採用餓漢式,若對資源十分在意可以採用靜態內部類,不建議採用懶漢式及雙重檢測