Java設(shè)計(jì)模式-單例模式

一、什么是單例模式

保證一個(gè)類僅有一個(gè)實(shí)例,并提供了一個(gè)訪問他的全局訪問點(diǎn)。其目的是保證整個(gè)應(yīng)用中只存在類的唯一實(shí)例。

比如我們?cè)谙到y(tǒng)啟動(dòng)時(shí),需要加載一些公共的配置信息,對(duì)整個(gè)應(yīng)用程序的整個(gè)生命周期中都可見且唯一,這時(shí)需要設(shè)計(jì)成單例模式。如:Spring容器,session工廠,緩存,數(shù)據(jù)庫(kù)連接池等等。

二、單例的實(shí)現(xiàn)主要是通過以下兩個(gè)步驟:

1,將該類的構(gòu)造方法定義為私有方法,這樣其他處的代碼就無法通過調(diào)用該類的構(gòu)造方法來實(shí)例化該類的對(duì)象,只有通過該類提供的靜態(tài)方法來得到該類的唯一實(shí)例;

2,在該類內(nèi)提供一個(gè)靜態(tài)方法,當(dāng)我們調(diào)用這個(gè)方法時(shí),如果類持有的引用不為空就返回這個(gè)引用,如果類保持的引用為空就創(chuàng)建該類的實(shí)例并將實(shí)例的引用賦予該類保持的引。

三、適用場(chǎng)景

1,需要生成唯一序列的環(huán)境

2,需要頻繁實(shí)例化然后銷毀代碼的對(duì)象

3,有狀態(tài)的工具類對(duì)象

4,頻繁訪問數(shù)據(jù)庫(kù)或文件的對(duì)象

以下都是單例模式的經(jīng)典實(shí)用場(chǎng)景

1,資源共享的情況下,避免由于資源操作時(shí)導(dǎo)致的性能活損耗。如日志文件,應(yīng)用配置

2,控制資源的情況下,方便資源之間的相互通信。如線程池等。

應(yīng)用場(chǎng)景舉例:

1,外部資源:每臺(tái)計(jì)算機(jī)有若干個(gè)打印機(jī),但是只能有一個(gè)PrinterSpooler,以避免兩個(gè)打印作業(yè)同時(shí)輸出到打印機(jī)。

內(nèi)部資源:大多數(shù)軟件都有一個(gè)或者多個(gè)屬性文件存放系統(tǒng)配置,這樣的系統(tǒng)應(yīng)該有一個(gè)對(duì)象管理這些屬性文件

2,Windows的Task Manager(任務(wù)管理器)就是很典型的單例模式,我們不能同事打開兩個(gè)windows task manager

3,windows的回收站也是典型的單例應(yīng)用,回收站一直維護(hù)著僅有的一個(gè)實(shí)例

4,網(wǎng)站的計(jì)數(shù)器,一般也是采用單例模式來實(shí)現(xiàn),否則難同步

5,應(yīng)用程序的日志應(yīng)用,一般都用單例模式,由于共享的日志文件一直處于打開狀態(tài),因?yàn)橹荒苡幸粋€(gè)實(shí)例去操作,否則內(nèi)容不好追加

6,Web應(yīng)用的配置對(duì)象的讀取,一般也應(yīng)用單例模式,這個(gè)是由于配置文件是共享資源。

7,數(shù)據(jù)庫(kù)連接池的設(shè)計(jì)一般也是采用單例模式,因?yàn)閿?shù)據(jù)庫(kù)連接是一種數(shù)據(jù)庫(kù)資源。數(shù)據(jù)庫(kù)軟件系統(tǒng)中使用數(shù)據(jù)庫(kù)連接池,主要是節(jié)省打開或者關(guān)閉數(shù)據(jù)庫(kù)連接索引引起的效率損耗,這種效率上的消耗是非常昂貴的,因?yàn)橛脝卫J絹砭S護(hù),就可以大大降低這種損耗。

8,多線程的線程池的設(shè)計(jì)一般也是采用單例模式,這是由于線程池要方便對(duì)池中的線程進(jìn)行控制

9,操作系統(tǒng)的文件系統(tǒng),也是大的單例模式實(shí)現(xiàn)的具體例子,一個(gè)操作系統(tǒng)只能有一個(gè)文件系統(tǒng)。

10,HttpApplication也是單例的典型應(yīng)用。熟悉ASP.Net(IIS)的整個(gè)請(qǐng)求生命周期的人應(yīng)該知道,HttpApplication也是單例模式,所有的HttpModule都共享一個(gè)HttpApplication實(shí)例。


四、如何保證實(shí)例的唯一

1,防止外部初始化

2,由類本身進(jìn)行實(shí)例化

3,保證實(shí)例化一次

4,對(duì)外提供獲取實(shí)例的對(duì)象

5,線程返券

五、單例模式的優(yōu)缺點(diǎn)

優(yōu)點(diǎn):

內(nèi)存中只有一個(gè)對(duì)象,節(jié)省內(nèi)存空間

避免頻繁的創(chuàng)建銷毀對(duì)象,可以提高性能

避免對(duì)資源的多重占用,簡(jiǎn)化訪問

為整個(gè)系統(tǒng)提供一個(gè)全局訪問點(diǎn)

缺點(diǎn):

不適用于變化頻繁的對(duì)象;

濫用單利將帶來一些問題,如為了節(jié)省資源將數(shù)據(jù)庫(kù)連接池對(duì)象設(shè)計(jì)為的單利類,可能會(huì)導(dǎo)致共享連接池對(duì)象的程序過多而出現(xiàn)連接池溢出;

如果實(shí)例化的對(duì)象長(zhǎng)時(shí)間不被利用,系統(tǒng)會(huì)認(rèn)為該對(duì)象是垃圾而被回收,這可能會(huì)導(dǎo)致對(duì)象狀態(tài)的丟失;

六、單例模式的實(shí)現(xiàn)

1,餓漢式

// 餓漢式單例

public class Singleton1 {

? ? // 指向自己實(shí)例的私有靜態(tài)引用,主動(dòng)創(chuàng)建

? ? private static Singleton1 singleton1 = new Singleton1();

? ? // 私有的構(gòu)造方法

? ? private Singleton1(){}

? ? // 以自己實(shí)例為返回值的靜態(tài)的公有方法,靜態(tài)工廠方法

? ? public static Singleton1 getSingleton1(){

? ? ? ? return singleton1;

? ? }

}

我們知道,類加載的方式是需加載,切加載一次。。因此,在上述單例被加載時(shí),就會(huì)實(shí)例化一個(gè)對(duì)象并交給自己的引用,供系統(tǒng)使用;而且,由于來在整個(gè)生命周期中只會(huì)被加載一次,因此只會(huì)創(chuàng)建一個(gè)實(shí)例,即能夠充分保證單例。

優(yōu)點(diǎn):這種寫法比較簡(jiǎn)單,就是在類裝載的時(shí)候就完成實(shí)例化。避免了線程同步問題。

缺點(diǎn):在類裝載的時(shí)候就完成實(shí)例化,沒有達(dá)到Lazy Loading的效果。如果喲偶的類從始至終都未使用過這個(gè)實(shí)例,則會(huì)造成內(nèi)存浪費(fèi)。

2,懶漢式

// 懶漢式單例

public class Singleton2 {

? ? // 指向自己實(shí)例的私有靜態(tài)引用

? ? private static Singleton2 singleton2;

? ? // 私有的構(gòu)造方法

? ? private Singleton2(){}

? ? // 以自己實(shí)例為返回值的靜態(tài)的公有方法,靜態(tài)工廠方法

? ? public static Singleton2 getSingleton2(){

? ? ? ? // 被動(dòng)創(chuàng)建,在真正需要使用時(shí)才去創(chuàng)建

? ? ? ? if (singleton2 == null) {

? ? ? ? ? ? singleton2 = new Singleton2();

? ? ? ? }

? ? ? ? return singleton2;

? ? }

}

我們從懶漢式單例可以看到,單例實(shí)例被延遲加載,即只有在真正使用的時(shí)候才會(huì)實(shí)例化一個(gè)對(duì)象并交給自己的引用。

這種寫法起到了Lazy Loading的效果,但是只能在單線程下使用。如果多線程下,一個(gè)線程進(jìn)入了if(singletion==null)判斷語(yǔ)句塊,還未來得及往下執(zhí)行,另一個(gè)線程也通過了這個(gè)判斷語(yǔ)句,這時(shí)變回產(chǎn)生多個(gè)實(shí)例。所以在多線程下不可用這種方式。

3,接下來對(duì)它進(jìn)行線程安全改造

(1)同步鎖

public synchronized static Singleton getInstance() {

if (singleton == null) {

singleton = new Singleton();

}

return singleton;

}

優(yōu)點(diǎn):線程安全

缺點(diǎn):每次獲取實(shí)例都要枷鎖,耗費(fèi)資源,其實(shí)主要實(shí)例已經(jīng)生成,以后獲取就不需要在鎖了。

(2)雙重檢查鎖

public class Singleton

? ? {

? ? ? ? private static Singleton instance;

? ? ? ? //程序運(yùn)行時(shí)創(chuàng)建一個(gè)靜態(tài)只讀的進(jìn)程輔助對(duì)象

? ? ? ? private static readonly object syncRoot = new object();

? ? ? ? private Singleton() { }

? ? ? ? public static Singleton GetInstance()

? ? ? ? { //先判斷是否存在,不存在再加鎖處理

? ? ? ? ? if (instance == null)

? ? ? ? ? ? {

? ? ? ? ? ? ? ? //在同一個(gè)時(shí)刻加了鎖的那部分程序只有一個(gè)線程可以進(jìn)入

? ? ? ? ? ? ? ? lock (syncRoot)

? ? ? ? ? ? ? ? {

? ? ? ? ? ? ? ? ? ? if (instance == null)

? ? ? ? ? ? ? ? ? ? {

? ? ? ? ? ? ? ? ? ? ? ? instance = new Singleton();

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? ? ? return instance;

? ? ? ? }

? ? }


public static Singleton getInstance() {

if (singleton == null) {

synchronized (Singleton.class) {

if (singleton == null) {

singleton = new Singleton();

}

}

}

return singleton;

}

代碼中我們進(jìn)行了兩次if(singletion==null)檢查,這樣就可以保證線程安全了。這樣,實(shí)例化代碼只執(zhí)行一次,后面再次訪問時(shí),判斷if(singletion==null)直接return實(shí)例化對(duì)象。

使用了雙重檢測(cè)同步延遲加載去創(chuàng)建單例的做法是一個(gè)非常優(yōu)秀的做法,其不但保證了單例,而且切實(shí)提高了程序運(yùn)行效率

優(yōu)點(diǎn):線程安全;延遲加載;效率較高。

(3)靜態(tài)內(nèi)部類

public class Singleton {

private Singleton() {

}


private static class SingletonHolder {

private static final Singleton singleton = new Singleton();

}


public static Singleton getInstance() {

return SingletonHolder.singleton;

}

}

優(yōu)點(diǎn):既避免了同步帶來的性能損耗,又能夠延遲加載



https://www.cnblogs.com/jiangkuan/p/6031040.html

https://www.cnblogs.com/lcxdevelop/p/11080895.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容