相信大家都知道設(shè)計模式洁段,聽的最多的也應(yīng)該是單例設(shè)計模式祠丝,這種模式也是在開發(fā)中用的最多的設(shè)計模式,可能有很多人會寫幾種設(shè)計模式岸蜗,那么你是否知道什么是設(shè)計模式璃岳?為什么會有單例設(shè)計模式即它的作用是什么悔捶?單例模式有哪些寫法蜕该?對于這樣的問題堂淡,可能有部分童鞋并不能很好的回答,沒關(guān)系今天就和大家一起來詳細的學(xué)習(xí)下單例設(shè)計模式萤悴,相信通過學(xué)習(xí)本篇你將對單例設(shè)計模式有個詳細的理解覆履。如有謬誤歡迎批評指正内狗,如有疑問歡迎留言。
通過本篇博客你將學(xué)到以下內(nèi)容
①什么是設(shè)計模式
②為什么會有單例設(shè)計模式即它的用處,以及它解決了什么問題
③怎樣實現(xiàn)單例,即它的設(shè)計思想是什么
④單例模式有哪些寫法
⑤單例模式在面試中要注意哪些事項
1、什么是設(shè)計模式赂鲤?
? ? 首先我們來看第一個問題什么是設(shè)計模式数初?在百度百科中它的定義是這樣的:?設(shè)計模式(Design pattern)是一套被反復(fù)使用梗顺、多數(shù)人知曉的寺谤、經(jīng)過分類編目的变屁、代碼設(shè)計經(jīng)驗的總結(jié)。(百度百科)
? ? 其實設(shè)計模式是人們實踐的產(chǎn)物疮胖,在初期的開發(fā)過程中好多人發(fā)現(xiàn)再進行重復(fù)的代碼書寫澎灸,那些開發(fā)大牛們就不斷總結(jié)性昭、抽取最終得到了大家的認可于是就產(chǎn)生了設(shè)計模式巩梢,其實設(shè)計模式的種類可以分為23種左右括蝠,今天主要和大家一起學(xué)習(xí)一下單例設(shè)計模式忌警,因為這種設(shè)計模式是使用的最多的設(shè)計模式。在以后的文章中會給大家?guī)砥渌J降挠懻摗?/p>
2箕速、為什么會有單例設(shè)計模式盐茎?
? 我們都知道單例模式是在開發(fā)中用的最多的一種設(shè)計模式字柠,那么究竟為什么會有單例設(shè)計模式呢窑业?對于這個問題相信有很多會寫單例的人都會有個這個疑問枕屉。在這里先說一下單例的用途,然后舉一個例子大家就會明白為什么會有單例了西潘。單例模式主要是為了避免因為創(chuàng)建了多個實例造成資源的浪費秸架,且多個實例由于多次調(diào)用容易導(dǎo)致結(jié)果出現(xiàn)錯誤东抹,而使用單例模式能夠保證整個應(yīng)用中有且只有一個實例缭黔。從其名字中我們就可以看出所謂單例馏谨,就是單個實例也就是說它可以解決的問題是:可以保證一個類在內(nèi)存中的對象的唯一性惧互,在一些常用的工具類喊儡、線程池艾猜、緩存匆赃,數(shù)據(jù)庫算柳,賬戶登錄系統(tǒng)瞬项、配置文件等程序中可能只允許我們創(chuàng)建一個對象滥壕,一方面如果創(chuàng)建多個對象可能引起程序的錯誤绎橘,另一方面創(chuàng)建多個對象也造成資源的浪費称鳞。在這種基礎(chǔ)之上單例設(shè)計模式就產(chǎn)生了因為使用單例能夠保證整個應(yīng)用中有且只有一個實例冈止,看到這大家可能有些疑惑熙暴,沒關(guān)系周霉,我們來舉一個例子俱箱,相信看完后你就會非常明白狞谱,為什么會有單例跟衅。
假如有一個有這么一個需求与斤,有一個類A和一個類B它們共享配置文件的信息磷支,在這個配置文件中有很多數(shù)據(jù)如下圖所示
如上圖所示現(xiàn)在類ConfigFile中存在共享的數(shù)據(jù)Num1雾狈,Num2善榛,Num3等移盆。假如在類A中修改ConfigFile中數(shù)據(jù),在類A中應(yīng)該有如下代碼
ConfigFile configFile=new ConfigFile();
configFile. Num1=2;
這個時候configFile中的Num1=2绞愚,但是請注意這里是new ConfigFile是一個對象叙甸,想象一下在進行了上述操作后類B中進行如下操作
ConfigFile configFile=new ConfigFile();
System. out.println("configFile.Num1=" +configFile.Num1);
即直接new ConfigFile();然后打印Num1,大家思考一下這時候打印出的數(shù)據(jù)為幾?我想你應(yīng)該知道它打印的結(jié)果是這樣的:configFile.Num1=1;也就是說因為每次調(diào)用都創(chuàng)建了一個ConfigFile對象位衩,所以導(dǎo)致了在類A中的修改并不會真正改變ConfigFile中的值裆蒸,它所更改的只是在類A中說創(chuàng)建的那個對象的值。假如現(xiàn)在要求在類A中修改數(shù)據(jù)后糖驴,要通知類B僚祷,即在類A和類B中操作的數(shù)據(jù)是同一個數(shù)據(jù),類A改變一個數(shù)據(jù)遂赠,類B也會得到這個數(shù)據(jù)久妆,并在類A修改后的基礎(chǔ)上進行操作,那么我們應(yīng)該怎么做呢?看到這大家可能會說so easy,把ConfigFile中的數(shù)據(jù)設(shè)置為靜態(tài)不就Ok了嗎跷睦?對蜕乡,有這種想法很好反症,這樣做也沒有錯。但是我們都知道靜態(tài)數(shù)據(jù)的生命周期是很長的,假如ConfigFile中有很多數(shù)據(jù)時,如果將其全部設(shè)成靜態(tài)的,那將是對內(nèi)存的極大損耗。所以全部設(shè)置成靜態(tài)雖然可行但并不是一個很好的解決方法。那么我們應(yīng)該怎么做呢?要想解決上面的問題,其實不難,只要能保證對象是唯一的就可以解決上面的問題,那么問題來了如何保證對象的唯一性呢?這樣就需要用單例設(shè)計模式了。
3倾贰、單例模式的設(shè)計思想
在上面我們說到現(xiàn)在解決問題的關(guān)鍵就是保證在應(yīng)用中只有一個對象就行了架忌,那么怎么保證只有一個對象呢?
其實只需要三步就可以保證對象的唯一性
(1)不允許其他程序用new對象。
? ? 因為new就是開辟新的空間,在這里更改數(shù)據(jù)只是更改的所創(chuàng)建的對象的數(shù)據(jù),如果可以new的話泊藕,每一次new都產(chǎn)生一個對象讼呢,這樣肯定保證不了對象的唯一性窜管。
(2)在該類中創(chuàng)建對象
因為不允許其他程序new對象失乾,所以這里的對象需要在本類中new出來
(3)對外提供一個可以讓其他程序獲取該對象的方法
? ?因為對象是在本類中創(chuàng)建的纽竣,所以需要提供一個方法讓其它的類獲取這個對象。
那么這三步怎么用代碼實現(xiàn)呢港令?將上述三步轉(zhuǎn)換成代碼描述是這樣的
(1)私有化該類的構(gòu)造函數(shù)
(2)通過new在
本類中創(chuàng)建一個本類對象
(3)定義一個公有的方法淋淀,將在該類中所創(chuàng)建的對象返回
4、單例模式的寫法
經(jīng)過3中的分析我們理解了單例所解決的問題以及它的實現(xiàn)思想,接著來看看它的實現(xiàn)代碼党瓮,單例模式的寫法大的方面可以分為5種五種①懶漢式②餓漢式③雙重校驗鎖④靜態(tài)內(nèi)部類⑤枚舉呛谜。接下來我們就一起來看看這幾種單例設(shè)計模式的代碼實現(xiàn)齐帚,以及它們的優(yōu)缺點
4.1單例模式的餓漢式[可用]
public class Singleton {
? private static Singleton instance=new Singleton();
? private Singleton(){};
? public static Singleton getInstance(){
? ? return instance;
? }
}
訪問方式
Singleton instance = Singleton.getInstance();
得到這個實例后就可以訪問這個類中的方法了愉豺。
優(yōu)點:從它的實現(xiàn)中我們可以看到,這種方式的實現(xiàn)比較簡單锹锰,在類加載的時候就完成了實例化茂装,避免了線程的同步問題训挡。
缺點:由于在類加載的時候就實例化了茅特,所以沒有達到Lazy Loading(懶加載)的效果,也就是說可能我沒有用到這個實例胚鸯,但是它
也會加載,會造成內(nèi)存的浪費(但是這個浪費可以忽略,所以這種方式也是推薦使用的)愧哟。
4.2單例模式的餓漢式變換寫法[可用]
public class Singleton{
? private static Singleton instance = null;
? static {
? ? instance = new Singleton();
? }
? private Singleton() {};
? public static Singleton getInstance() {
? ? return instance;
? }
}
訪問方式:
Singleton instance = Singleton.getInstance();
得到這個實例后就可以訪問這個類中的方法了。
可以看到上面的代碼是按照在2中分析的那三步來實現(xiàn)的名挥,這中寫法被稱為餓漢式鞋既,因為它在類創(chuàng)建的時候就已經(jīng)實例化了對象。其實4.2和4.1只是寫法有點不同田弥,都是在類初始化時創(chuàng)建對象的辜妓,它的優(yōu)缺點和4.1一樣,可以歸為一種寫法骚揍。
4.3單例模式的懶漢式[線程不安全,不可用]
public class Singleton {
? private static Singleton instance=null;
? private Singleton() {};
? public static Singleton getInstance(){
? ? if(instance==null){
? ? ? instance=new Singleton();
? ? }
? ? return instance;
? }
}
這種方式是在調(diào)用getInstance方法的時候才創(chuàng)建對象的糊昙,所以它比較懶因此被稱為懶漢式回挽。
在上述兩種寫法中懶漢式其實是存在線程安全問題的涡驮,喜歡刨根問題的同學(xué)可能會問捉捅,存在怎樣的線程安全問題内舟?怎樣導(dǎo)致這種問題的耕蝉?好柒瓣,我們來說一下什么情況下這種寫法會有問題。在運行過程中可能存在這么一種情況:有多個線程去調(diào)用getInstance方法來獲取Singleton的實例傍药,那么就有可能發(fā)生這樣一種情況當(dāng)?shù)谝粋€線程在執(zhí)行if(instance==null)這個語句時屹培,此時instance是為null的進入語句。在還沒有執(zhí)行instance=new Singleton()時(此時instance是為null的)第二個線程也進入if(instance==null)這個語句怔檩,因為之前進入這個語句的線程中還沒有執(zhí)行instance=new Singleton()褪秀,所以它會執(zhí)行instance=new Singleton()來實例化Singleton對象,因為第二個線程也進入了if語句所以它也會實例化Singleton對象薛训。這樣就導(dǎo)致了實例化了兩個Singleton對象媒吗。所以單例模式的懶漢式是存在線程安全問題的,既然它存在問題乙埃,那么可能有解決這個問題的方法闸英,那么究竟怎么解決呢?對這種問題可能很多人會想到加鎖于是出現(xiàn)了下面這種寫法介袜。
4.4懶漢式線程安全的[線程安全甫何,效率低不推薦使用]
public class Singleton {
? private static Singleton instance=null;
? private Singleton() {};
? public static synchronized Singleton getInstance(){
? ? if(instance==null){
? ? ? instance=new Singleton();
? ? }
? ? return instance;
? }
}
缺點:效率太低了,每個線程在想獲得類的實例時候遇伞,執(zhí)行g(shù)etInstance()方法都要進行同步辙喂。而其實這個方法只執(zhí)行一次實例化代碼就夠了,后面的想獲得該類實例鸠珠,直接return就行了巍耗。方法進行同步效率太低要改進。
4.5單例模式懶漢式[線程不安全渐排,不可用]
對于上述缺陷的改進可能有的人會想到如下的代碼
public class Singleton7 {
? private static Singleton instance=null;
? public static Singleton getInstance() {
? ? if (instance == null) {
? ? ? synchronized (Singleton.class) {
? ? ? ? instance = new Singleton();
? ? ? }
? ? }
? ? return instance;
? }
}
其實這種寫法跟4.3一樣是線程不安全的炬太,當(dāng)一個線程還沒有實例化Singleton時另一個線程執(zhí)行到if(instance==null)這個判斷語句時就會進入if語句,雖然加了鎖驯耻,但是等到第一個線程執(zhí)行完instance=new Singleton()跳出這個鎖時亲族,另一個進入if語句的線程同樣會實例化另外一個Singleton對象炒考,線程不安全的原理跟4.3類似。因此這種改進方式并不可行霎迫,經(jīng)過大神們一步一步的探索票腰,寫出了懶漢式的雙重校驗鎖。
4.6單例模式懶漢式雙重校驗鎖[推薦用]
public class Singleton {
? /**
? * 懶漢式變種女气,屬于懶漢式中最好的寫法,保證了:延遲加載和線程安全
? */
? private static volatile Singleton instance=null;
? private Singleton() {};
? public static Singleton getInstance(){
? ? if (instance == null) {?
? ? ? ? ? ? synchronized (Singleton.class) {?
? ? ? ? ? ? ? ? if (instance == null) {?
? ? ? ? ? ? ? ? ? instance = new Singleton();?
? ? ? ? ? ? ? ? }?
? ? ? ? ? ? }?
? ? ? ? }?
? ? ? ? return instance;?
? }
}
訪問方式
Singleton instance = Singleton.getInstance();
得到這個實例后就可以訪問這個類中的方法了测柠。
Double-Check概念對于多線程開發(fā)者來說不會陌生炼鞠,如代碼中所示,我們進行了兩次if (instance== null)檢查轰胁,這樣就可以保 ? ?證線程安全了谒主。這樣,實例化代碼只用執(zhí)行一次赃阀,后面再次訪問時霎肯,判斷if (instance== null),直接return實例化對象榛斯。
優(yōu)點:線程安全观游;延遲加載;效率較高驮俗。
4.7內(nèi)部類[推薦用]
public class Singleton{
? private Singleton() {};
? private static class SingletonHolder{
? ? private static Singleton instance=new Singleton();
? }
? public static Singleton getInstance(){
? ? return SingletonHolder.instance;
? }
}
訪問方式
Singleton instance = Singleton.getInstance();
得到這個實例后就可以訪問這個類中的方法了懂缕。
? ? 這種方式跟餓漢式方式采用的機制類似,但又有不同王凑。兩者都是采用了類裝載的機制來保證初始化實例時只有一個線程搪柑。不同
的地方在餓漢式方式是只要Singleton類被裝載就會實例化,沒有Lazy-Loading的作用索烹,而靜態(tài)內(nèi)部類方式在Singleton類被裝載時
并不會立即實例化工碾,而是在需要實例化時,調(diào)用getInstance方法百姓,才會裝載SingletonHolder類渊额,從而完成Singleton的實例化。
類的靜態(tài)屬性只會在第一次加載類的時候初始化垒拢,所以在這里端圈,JVM幫助我們保證了線程的安全性,在類進行初始化時子库,別的線程是
無法進入的舱权。
優(yōu)點:避免了線程不安全,延遲加載仑嗅,效率高宴倍。
4.8枚舉[極推薦使用]
public enum SingletonEnum {
? instance;
? private SingletonEnum() {}
? public void method(){
? }
}
訪問方式
SingletonEnum.instance.method();
可以看到枚舉的書寫非常簡單张症,訪問也很簡單在這里SingletonEnum.instance這里的instance即為SingletonEnum類型的引用所以得到它就可以調(diào)用枚舉中的方法了。
借助JDK1.5中添加的枚舉來實現(xiàn)單例模式鸵贬。不僅能避免多線程同步問題俗他,而且還能防止反序列化重新創(chuàng)建新的對象±疲可能是因為枚舉在JDK1.5中才添加兆衅,所以在實際項目開發(fā)中,很少見人這么寫過嗜浮,這種方式也是最好的一種方式羡亩,如果在開發(fā)中JDK滿足要求的情況下建議使用這種方式。
5危融、總結(jié)
? ? ?在真正的項目開發(fā)中一般采用4.1畏铆、4.6、4.7吉殃、4.8看你最喜歡哪種寫法了辞居,一般情況下這幾種模式是沒有問題的,為了裝逼我一般采用4.6這種寫法蛋勺,我們經(jīng)常用的Android-Universal-Image-Loader這個開源項目也是采用的4.6這種寫法瓦灶,其實最安全的寫法是4.8即枚舉,它的實現(xiàn)非常簡單而且最安全可謂很完美抱完,但是可能是因為只支持JDK1.5吧又或者是因為枚舉大家不熟悉所以目前使用的人并不多倚搬,但是大家可以嘗試下。另外當(dāng)我們使用反射機制時可能不能保證實例的唯一性乾蛤,但是枚舉始終可以保證唯一性每界,具體請參考次博客:blog.csdn.net/java2000_ne…但是一般情況下很少遇到這種情況。
6家卖、單例模式的在面試中的問題
? ? ?單例模式在面試中會常常的被遇到眨层,因為它是考擦一個程序員的基礎(chǔ)的扎實程度的,如果說你跟面試官說你做過項目上荡,面試官讓你寫幾個單例設(shè)計模式趴樱,你寫不出來,你覺著面試官會相信嗎酪捡?在面試時一定要認真準(zhǔn)備每一次面試叁征,靠忽悠即使你被錄取了,你也很有可能會對這個公司不滿意逛薇,好了我們言歸正傳捺疼,其實單例設(shè)計模式在面試中很少有人會問餓漢式寫法,一般都會問單例設(shè)計模式的懶漢式的線程安全問題永罚,所以大家一定要充分理解單例模式的線程安全的問題啤呼,就這幾種模式花點時間卧秘,認真學(xué)透,面試中遇到任何關(guān)于單例模式的問題你都不會害怕是吧官扣。
轉(zhuǎn)載自:blog.csdn.net/dmk877/arti…