一、單例設(shè)計(jì)模式
就是一個(gè)類(lèi)只允許創(chuàng)建一個(gè)對(duì)象,那么我們稱(chēng)該類(lèi)為單例類(lèi),這種設(shè)計(jì)模式我們稱(chēng)為單例模式。
二、為什么使用單例模式
資源共享:有些類(lèi)擁有共享的資源,例如數(shù)據(jù)庫(kù)連接池、線(xiàn)程池、緩存等。使用單例模式確保只有一個(gè)實(shí)例,避免資源浪費(fèi)和競(jìng)爭(zhēng)條件。
線(xiàn)程安全:?jiǎn)卫J娇梢杂脕?lái)保證多線(xiàn)程環(huán)境下只有一個(gè)實(shí)例,從而避免競(jìng)爭(zhēng)條件和不一致性。
避免全局變量:使用單例模式可以減少全局變量的數(shù)量,從而降低了不同部分之間的耦合。這有助于使代碼更加可維護(hù)和可測(cè)試。
全局訪(fǎng)問(wèn)點(diǎn):?jiǎn)卫J教峁┝艘粋€(gè)全局的訪(fǎng)問(wèn)點(diǎn),使得其他對(duì)象可以輕松地獲取該實(shí)例,而無(wú)需創(chuàng)建多個(gè)實(shí)例或傳遞實(shí)例引用。這對(duì)于配置管理、日志記錄、事件管理等非常有用。
三、如何實(shí)現(xiàn)單例
常見(jiàn)的單例設(shè)計(jì)模式有以下五種:
1、餓漢式
2、懶漢式
3、雙重檢查鎖
4、靜態(tài)內(nèi)部類(lèi)
5、枚舉
我們?cè)诰帉?xiě)以上五種單例代碼的時(shí)候需要注意以下幾點(diǎn):
1、構(gòu)造器私有化
2、暴露一個(gè)公共方法獲取單例對(duì)象
3、是否考慮線(xiàn)程安全
4、是否考慮加載時(shí)間
對(duì)于1、2兩點(diǎn)來(lái)說(shuō),我常常是這樣想的,一個(gè)單例類(lèi)要有兩個(gè)private、兩個(gè)static。構(gòu)造器和對(duì)象必須用private修飾,對(duì)象和獲取對(duì)象的方法必須用static修飾。
注:1、將對(duì)象定義為static的主要原因有兩個(gè),其一是確保只有一個(gè)實(shí)例可以被全局訪(fǎng)問(wèn)。static成員變量是屬于類(lèi)而不是實(shí)例的,這意味著不管創(chuàng)建多少個(gè)類(lèi)的實(shí)例,它們都共享相同的static成員變量。這對(duì)于單例模式非常重要,因?yàn)閱卫暮诵哪繕?biāo)是確保只有一個(gè)實(shí)例存在。其二是返回對(duì)象的方法是靜態(tài)的,靜態(tài)方法不能訪(fǎng)問(wèn)非靜態(tài)成員變量。
我們先來(lái)看第一個(gè)單例模式的代碼
四、餓漢式
餓漢式的實(shí)現(xiàn)方式比較簡(jiǎn)單。具體的代碼實(shí)現(xiàn)如下:
public class EagerSingleton {
private final static EagerSingleton instance = new EagerSingleton();
private EagerSingleton() {}
public static EagerSingleton getInstance(){
return instance;
}
}
從上述代碼可以看出,在類(lèi)加載的時(shí)候,instance 靜態(tài)實(shí)例就已經(jīng)創(chuàng)建并初始化好了,所以,instance 實(shí)例的創(chuàng)建過(guò)程是線(xiàn)程安全的。如果不需要延遲加載,并且確保在多線(xiàn)程環(huán)境下只有一個(gè)實(shí)例存在,餓漢式單例是一種有效的選擇。
五、懶漢式
有餓漢式,對(duì)應(yīng)地,就有懶漢式。懶漢式相對(duì)于餓漢式的優(yōu)勢(shì)是支持延遲加載,具體的代碼實(shí)現(xiàn)如下:
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {}
public static LazySingleton getInstance(){
if(instance == null){
instance = new LazySingleton();
}
return instance;
}
}
以上的寫(xiě)法本質(zhì)上是有問(wèn)題,在多線(xiàn)程環(huán)境下,其實(shí)是無(wú)法保證其單例的特點(diǎn)的,很有可能會(huì)有超過(guò)一個(gè)線(xiàn)程同時(shí)執(zhí)行了new Singleton();解決的辦法簡(jiǎn)單粗暴,直接加鎖,代碼如下:
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {}
public static synchronized LazySingleton getInstance(){
if(instance == null){
instance = new LazySingleton();
}
return instance;
}
}
以上的寫(xiě)法確實(shí)可以保證jvm中有且僅有一個(gè)單例實(shí)例存在,但是方法上加鎖會(huì)極大 的降低獲取單例對(duì)象的并發(fā)度。同一時(shí)間只有一個(gè)線(xiàn)程可以獲取單例對(duì)象,為了解決 以上的方案則有了第三種寫(xiě)法。
六、雙重檢查鎖
餓漢式不支持延遲加載,懶漢式有性能問(wèn)題,不支持高并發(fā)。那我們?cè)賮?lái)看一種既支 持延遲加載、又支持高并發(fā)的單例實(shí)現(xiàn)方式,也就是雙重檢測(cè)實(shí)現(xiàn)方式(辦法總比困難多嘛):在這種實(shí)現(xiàn)方式中,只要 instance 被創(chuàng)建之后,即便再調(diào)用 getInstance() 函數(shù)也不會(huì)再進(jìn)入到加鎖邏輯中了。所以,這種實(shí)現(xiàn)方式解決了懶漢式并發(fā)度低的問(wèn)題。具體的代碼實(shí)現(xiàn)如下:
public class DoubleCheckSingleton {
private volatile static DoubleCheckSingleton instance;
private DoubleCheckSingleton() {}
public static DoubleCheckSingleton getInstance(){
if(instance == null){
synchronized (DoubleCheckSingleton.class){
if(instance == null){
instance = new DoubleCheckSingleton();
}
}
}
return instance;
}
}
volatile: 加volatile關(guān)鍵字是為了保證順序性,因?yàn)樵贘ava中創(chuàng)建對(duì)象通常需要三個(gè)主要步驟,分別為分配內(nèi)存空間、初始化對(duì)象、引用賦值,正是因?yàn)閯?chuàng)建需要這三步,導(dǎo)致在多線(xiàn)程環(huán)境下,出現(xiàn)某些線(xiàn)程拿到的對(duì)象是半初始化對(duì)象。譬如:
線(xiàn)程1進(jìn)入雙重檢測(cè)鎖,執(zhí)行第一次檢測(cè),發(fā)現(xiàn)共享的實(shí)例變量為null,因此進(jìn)入同步塊。
線(xiàn)程1在同步塊內(nèi)部創(chuàng)建了單例對(duì)象,然后將實(shí)例變量指向新創(chuàng)建的對(duì)象。
由于指令重排序,創(chuàng)建對(duì)象的操作被移到了實(shí)例變量賦值之前。這樣,實(shí)例變量被賦值,但對(duì)象可能還沒(méi)有完全初始化。
線(xiàn)程2在第一次檢測(cè)時(shí),發(fā)現(xiàn)實(shí)例變量不為null,然后直接返回實(shí)例,但實(shí)例可能沒(méi)有完全初始化,導(dǎo)致不完整或不正確的對(duì)象。
七、靜態(tài)內(nèi)部類(lèi)
我們?cè)賮?lái)看一種比雙重檢測(cè)更加簡(jiǎn)單的實(shí)現(xiàn)方法,那就是利用 Java 的靜態(tài)內(nèi)部類(lèi)。 它有點(diǎn)類(lèi)似餓漢式,但又能做到了延遲加載。具體是怎么做到的呢?我們先來(lái)看它的代碼實(shí)現(xiàn)如下:
public class InnerSingleton {
private InnerSingleton(){}
public static InnerSingleton getInstance(){
return InnerSingletonHolder.instance;
}
// 靜態(tài)內(nèi)部類(lèi)實(shí)例化外部類(lèi)對(duì)象
private static class InnerSingletonHolder{
private final static InnerSingleton instance = new InnerSingleton();
}
}
InnerSingletonHolder 是一個(gè)靜態(tài)內(nèi)部類(lèi),當(dāng)外部類(lèi) InnerSingleton被加載的時(shí)候,并不會(huì)創(chuàng)建 InnerSingletonHolder 實(shí)例對(duì)象。只有當(dāng)調(diào)用 getInstance() 方法時(shí),
InnerSingletonHolder 才會(huì)被加載,這個(gè)時(shí)候才會(huì)創(chuàng)建 instance。insance 的唯一性、創(chuàng)建過(guò)程的線(xiàn)程安全性,都由 JVM 來(lái)保證。所以,這種實(shí)現(xiàn)方法既保證了線(xiàn)程安全,又能做到延遲加載。
八、枚舉
基于枚舉類(lèi)型的單例實(shí)現(xiàn)。這種實(shí)現(xiàn)方式通過(guò) Java 枚舉類(lèi)型本身的特性,保證了實(shí)例創(chuàng)建的線(xiàn)程安全性和實(shí)例的唯一性。具體的代碼如下:
public enum EnumSingleton {
// 對(duì)應(yīng)的枚舉類(lèi),任何一個(gè)枚舉項(xiàng)都是一個(gè)天然的單例
// 本質(zhì)上就是static final EnumSingleton instance = new EnumSingleton()
// 枚舉本身支持懶加載
INSTANCE;
}
更通用的寫(xiě)法如下:
public class EnumSingleton {
private EnumSingleton(){}
public static enum SinglentonEnum{
ESingleton;
private EnumSingleton instance = null;
private SinglentonEnum(){
instance = new EnumSingleton();
}
public EnumSingleton getInstance(){
return instance;
}
}
}
通過(guò)調(diào)用 EnumSingleton.SinglentonEnum.ESingleton.getInstance() 方法可以獲取單例對(duì)象。
單例模式就分享到這,希望可以幫助各位碼友!點(diǎn)個(gè)贊吧!?。?/h2>
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-724163.html
文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-724163.html
到了這里,關(guān)于設(shè)計(jì)模式之單例設(shè)計(jì)模式的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!