Java設(shè)計(jì)模式之單例模式及在Android中的重要使用

之前在開發(fā)中老用到一些設(shè)計(jì)模式可是呢又不是很懂,于是狠下心來琢磨一番。下面是我琢磨后總結(jié)的,希望對您有用。如果發(fā)現(xiàn)了問題,請幫忙指正。

一、單例模式是什么?

單例模式最初的定義出現(xiàn)于《設(shè)計(jì)模式》:“保證一個(gè)類僅有一個(gè)實(shí)例,并提供一個(gè)訪問它的全局訪問點(diǎn)。”
  Java中單例模式定義;“一個(gè)類有且僅有一個(gè)實(shí)例,并且自行實(shí)例化向整個(gè)系統(tǒng)提供該實(shí)例。”

二、為什么用單例模式?

對于系統(tǒng)中的某些類來說,只有一個(gè)實(shí)例很重要。例如,一個(gè)系統(tǒng)中可以存在多個(gè)打印任務(wù),但是只能有一個(gè)正在工作的任務(wù);一個(gè)系統(tǒng)只有有一個(gè)窗口管理器或文件系統(tǒng);一個(gè)系統(tǒng)只能有一個(gè)計(jì)時(shí)工具或ID生成器。如在Windows OS 中就只能打開一個(gè)任務(wù)管理器。如果不使用機(jī)制對窗口對象進(jìn)行唯一化,將彈出多個(gè)窗口,如果這些窗口顯示的內(nèi)容完全一致,則重復(fù)對象,浪費(fèi)內(nèi)存資源;如果這些窗口顯示的內(nèi)容不一致,則意味著某一瞬間系統(tǒng)有多個(gè)狀態(tài),與實(shí)際不符,也會為用戶帶來誤解,不知道哪一個(gè)才是真實(shí)的狀態(tài)。因此有時(shí)確保系統(tǒng)中某個(gè)對象的唯一性即一個(gè)類只能有一個(gè)實(shí)例是非常重要的。
  如何保證一個(gè)類只有一個(gè)實(shí)例并且這個(gè)實(shí)例易于被訪問呢?定義一個(gè)全局變量可以確保對象隨時(shí)都可以被訪問,但不能防止我們實(shí)例化多個(gè)對象。一個(gè)更好的解決辦法是讓類自身負(fù)責(zé)保存它的唯一實(shí)例。這個(gè)類可以保證沒有其他實(shí)例被創(chuàng)建,并且它可以提供一個(gè)訪問該實(shí)例的方法。這就是單例模式的模式動機(jī)。

三、單例模式特點(diǎn)

單例模式特點(diǎn)有三個(gè)
1、單例類只能有一個(gè)實(shí)例。
2、單例類必須自己創(chuàng)建自己的唯一實(shí)例。
3、單例類必須給其他對象(整個(gè)系統(tǒng))提供這一實(shí)例。
 從具體實(shí)現(xiàn)角度分析,一是單例模式的類只提供私有的(private)構(gòu)造函數(shù),二是類定義中含有一個(gè)該類的靜態(tài)私有(private static)對象,三是該類提供了一個(gè)靜態(tài)的(static)公有的(public)函數(shù)用于創(chuàng)建或獲取它本身的靜態(tài)私有對象。

四、Java中幾種常見單例模式寫法

通過上面的介紹你是不是對單例模式有了一個(gè)總的概念?沒有,那接下來繼續(xù)給你們放大招。
  基于單例模式特點(diǎn),單例對象通常作為程序中存放配置信息的載體(想想Android中的Application經(jīng)常在里面做一些配置的初始化),因?yàn)樗軌虮WC其他對象讀取到一致的信息。例如在某個(gè)服務(wù)器程序中,該服務(wù)器的配置信息可能存放在數(shù)據(jù)庫或 文件中,這些配置數(shù)據(jù)由某個(gè)單例對象統(tǒng)一讀取,服務(wù)進(jìn)程中的其他對象如果要獲取這些配置信息,只需訪問該單例對象即可。這種方式極大地簡化了在復(fù)雜環(huán)境 下,尤其是多線程環(huán)境下的配置管理,但是隨著應(yīng)用場景的不同,也可能帶來一些同步問題。

1、餓漢式單例
//餓漢式單例類.在類初始化時(shí),已經(jīng)自行實(shí)例化 
 public class Singleton {
     //私有的默認(rèn)構(gòu)造子
     private Singleton() {}
     //已經(jīng)自行實(shí)例化 
     private static final Singleton single = new Singleton();
     //靜態(tài)工廠方法 
     public static Singleton getInstance() {
         return single;
     }
 }

上面例子中,在這個(gè)類被加載時(shí),靜態(tài)變量single會被初始化,此時(shí)類的私有構(gòu)造子會被調(diào)用。這時(shí)單例類的唯一實(shí)例就被構(gòu)造出來了。
  餓漢式其實(shí)是一種比較形象的稱謂。既然餓,那么在創(chuàng)建對象實(shí)例的時(shí)候就比較著急,餓了嘛,于是在裝載類的時(shí)候就創(chuàng)建對象實(shí)例--->

private static final Singleton single = new Singleton();

餓漢式是典型的空間換時(shí)間,當(dāng)類裝載時(shí)就會創(chuàng)建類的實(shí)例,不管你用不用,先創(chuàng)建出來,然后每次調(diào)用的時(shí)候,就不需要再判斷,節(jié)省了運(yùn)行時(shí)間。

2、懶漢式單例
//懶漢式單例類.在第一次調(diào)用的時(shí)候?qū)嵗约?  
public class Singleton {  
    private Singleton() {}  
    private static Singleton single=null;  
    //靜態(tài)工廠方法   
    public static synchronized Singleton getInstance() {  
         if (single == null) {    
             single = new Singleton();  
         }    
        return single;  
    }  
}  

上面的懶漢式單例類實(shí)現(xiàn)里對靜態(tài)工廠方法使用了同步化,以處理多線程環(huán)境。
  懶漢式其實(shí)是一種比較形象的稱謂。既然懶,那么在創(chuàng)建對象實(shí)例的時(shí)候就不著急。會一直等到馬上要使用對象實(shí)例 的時(shí)候才會被創(chuàng)建,懶人嘛,總是推脫不開的時(shí)候才會真正執(zhí)行工作,因此在裝載對象的時(shí)候不創(chuàng)建對象實(shí)例。

private static Singleton single=null;  

懶漢式是典型的時(shí)間換空間,就是每次獲取實(shí)例都會進(jìn)行判斷,看是否需要?jiǎng)?chuàng)建實(shí)例,浪費(fèi)判斷的時(shí)間。當(dāng)然,如果一直沒有人使用的話,那就不會創(chuàng)建實(shí)例,則節(jié)約內(nèi)存空間
  由于懶漢式的實(shí)現(xiàn)是線程安全的,這樣會降低整個(gè)訪問的速度,而且每次都要判斷。那么有沒有更好的方式實(shí)現(xiàn)呢?

3、雙重檢查加鎖

可以使用“雙重檢查加鎖”的方式來實(shí)現(xiàn),就可以達(dá)到實(shí)現(xiàn)線程安全,又能使性能不受很大影響。
  雙重檢查加鎖:并不是每次進(jìn)入getInstance()都需要同步,而是先不同步,進(jìn)入方法后,先檢查單例對象是否存在,如果不存在才進(jìn)行下面的同步塊,這是第一重檢查,進(jìn)入同步塊后,再次檢查實(shí)例是否存在,如果不存在,就在同步的情況下創(chuàng)建一個(gè)實(shí)例(單例對象),這是第二重檢查。這樣就只需要同步一次,從而減輕了多次在同步情況下進(jìn)行判斷所浪費(fèi)的時(shí)間。
  “雙重檢查加鎖”機(jī)制的實(shí)現(xiàn)會使用關(guān)鍵字volatile,它的意思是:被volatile修飾的變量的值,將不會被本地線程緩存,所有對該變量的讀寫都是直接操作共享內(nèi)存,從而確保多個(gè)線程能正確的處理該變量。不清楚volatile的看過來volatile解析
代碼實(shí)例:

public class Singleton {
    private volatile static Singleton instance = null;
    private Singleton(){}
    public static Singleton getInstance(){
        //先檢查實(shí)例是否存在,如果不存在才進(jìn)入下面的同步塊
        if(instance == null){
            //同步塊,線程安全的創(chuàng)建實(shí)例
            synchronized (Singleton.class) {
                //再次檢查實(shí)例是否存在,如果不存在才真正的創(chuàng)建實(shí)例
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

這種實(shí)現(xiàn)方式既可以實(shí)現(xiàn)線程安全地創(chuàng)建實(shí)例,而又不會對性能造成太大的影響。它只是第一次創(chuàng)建實(shí)例的時(shí)候同步,以后就不需要同步了,從而加快了運(yùn)行速度。
  提示:由于volatile關(guān)鍵字可能會屏蔽掉虛擬機(jī)中一些必要的代碼優(yōu)化,所以運(yùn)行效率并不是很高。因此一般建議,沒有特別的需要,不要使用。也就是說,雖然可以使用“雙重檢查加鎖”機(jī)制來實(shí)現(xiàn)線程安全的單例,但并不建議大量采用,可以根據(jù)情況來選用。
  根據(jù)上面的分析,常見的兩種單例實(shí)現(xiàn)方式都存在小小的缺陷,那么有沒有一種方案,既能實(shí)現(xiàn)延遲加載,又能實(shí)現(xiàn)線程安全呢?那就是下面一種方法,放大招了,接著呦。

4、靜態(tài)內(nèi)部類
public class Singleton {
    
    private Singleton(){}
    /**
     *    類級的內(nèi)部類,也就是靜態(tài)的成員式內(nèi)部類,該內(nèi)部類的實(shí)例與外部類的實(shí)例
     *    沒有綁定關(guān)系,而且只有被調(diào)用到時(shí)才會裝載,從而實(shí)現(xiàn)了延遲加載。
     */
    private static class SingletonHolder{
        /**
         * 靜態(tài)初始化器,由JVM來保證線程安全
         */
        private static Singleton instance = new Singleton();
    }
    
    public static Singleton getInstance(){
        return SingletonHolder.instance;
    }
}

當(dāng)getInstance方法第一次被調(diào)用的時(shí)候,它第一次讀取SingletonHolder.instance,導(dǎo)致SingletonHolder類得到初始化;而這個(gè)類在裝載并被初始化的時(shí)候,會初始化它的靜態(tài)域,從而創(chuàng)建Singleton的實(shí)例,由于是靜態(tài)的域,因此只會在虛擬機(jī)裝載類的時(shí)候初始化一次,并由虛擬機(jī)來保證它的線程安全性。

5、單例和枚舉
public enum Singleton {
    /**
     * 定義一個(gè)枚舉的元素,它就代表了Singleton的一個(gè)實(shí)例。
     */
    
    uniqueInstance;
    
    /**
     * 單例可以有自己的操作
     */
    public void singletonOperation(){
        //功能處理
    }
}

按照《高效Java 第二版》中的說法:單元素的枚舉類型已經(jīng)成為實(shí)現(xiàn)Singleton的最佳方法。用枚舉來實(shí)現(xiàn)單例非常簡單,只需要編寫一個(gè)包含單個(gè)元素的枚舉類型即可。

對我來說,我比較喜歡第一種和第四種方式,簡單易懂。而且在JVM層實(shí)現(xiàn)了線程安全(如果不是多個(gè)類加載器環(huán)境)。一般的情況下,我會使用第一種方式,只有在要明確實(shí)現(xiàn)lazy loading效果時(shí)才會使用第四種方式

五、Android中典型的單例模式Application類

1、Application是什么?

Application和Activity,Service一樣,是android框架的一個(gè)系統(tǒng)組件,當(dāng)android程序啟動時(shí)系統(tǒng)會創(chuàng)建一個(gè) application對象,用來存儲系統(tǒng)的一些信息。通常我們是不需要指定一個(gè)Application的,這時(shí)系統(tǒng)會自動幫我們創(chuàng)建,如果需要?jiǎng)?chuàng)建自己 的Application,也很簡單創(chuàng)建一個(gè)類繼承 Application并在manifest的application標(biāo)簽中進(jìn)行注冊(只需要給Application標(biāo)簽增加個(gè)name屬性把自己的 Application的名字定入即可)。
  android系統(tǒng)會為每個(gè)程序運(yùn)行時(shí)創(chuàng)建一個(gè)Application類的對象且僅創(chuàng)建一個(gè),所以Application可以說是單例 (singleton)模式的一個(gè)類.且application對象的生命周期是整個(gè)程序中最長的,它的生命周期就等于這個(gè)程序的生命周期。因?yàn)樗侨?的單例的,所以在不同的Activity,Service中獲得的對象都是同一個(gè)對象。所以通過Application來進(jìn)行一些,數(shù)據(jù)傳遞,數(shù)據(jù)共享 等,數(shù)據(jù)緩存等操作。

2、巧妙運(yùn)用單例模式特點(diǎn),通過Application來傳遞數(shù)據(jù)

假如有一個(gè)Activity A, 跳轉(zhuǎn)到 Activity B ,并需要推薦一些數(shù)據(jù),通常的作法是Intent.putExtra() 讓Intent攜帶,或者有一個(gè)Bundle把信息加入Bundle讓Intent推薦Bundle對象,實(shí)現(xiàn)傳遞。但這樣作有一個(gè)問題在 于,Intent和Bundle所能攜帶的數(shù)據(jù)類型都是一些基本的數(shù)據(jù)類型,如果想實(shí)現(xiàn)復(fù)雜的數(shù)據(jù)傳遞就比較麻煩了,通常需要實(shí)現(xiàn) Serializable或者Parcellable接口。這其實(shí)是Android的一種IPC數(shù)據(jù)傳遞的方法。如果我們的兩個(gè)Activity在同一個(gè) 進(jìn)程當(dāng)中為什么還要這么麻煩呢,只要把需要傳遞的對象的引用傳遞過去就可以了。
基本思路是這樣的。在Application中創(chuàng)建一個(gè)HashMap ,以字符串為索引,Object為value這樣我們的HashMap就可以存儲任何類型的對象了。在Activity A中把需要傳遞的對象放入這個(gè)HashMap,然后通過Intent或者其它途經(jīng)再把這索引的字符串傳遞給Activity B ,Activity B 就可以根據(jù)這個(gè)字符串在HashMap中取出這個(gè)對象了。只要再向下轉(zhuǎn)個(gè)型 ,就實(shí)現(xiàn)了對象的傳遞。

六、總結(jié)

經(jīng)過網(wǎng)上的爬文終于了解了什么是單例模式,在這里感謝
http://www.cnblogs.com/java-my-life/archive/2012/03/31/2425631.html
http://blog.csdn.net/songylwq/article/details/6058771
http://www.blogjava.net/kenzhh/archive/2013/03/15/357824.html
http://www.cnblogs.com/hxsyl/archive/2013/03/19/2969489.html
http://blog.csdn.net/pi9nc/article/details/11200969

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,981評論 19 139
  • 單例模式(SingletonPattern)一般被認(rèn)為是最簡單、最易理解的設(shè)計(jì)模式,也因?yàn)樗暮啙嵰锥琼?xiàng)目中最...
    成熱了閱讀 4,298評論 4 34
  • 前言 本文主要參考 那些年,我們一起寫過的“單例模式”。 何為單例模式? 顧名思義,單例模式就是保證一個(gè)類僅有一個(gè)...
    tandeneck閱讀 2,539評論 1 8
  • 一.什么是單例模式 單例模式的定義:確保一個(gè)類只有一個(gè)實(shí)例,并提供一個(gè)訪問他的全局訪問點(diǎn)。單例模式是幾個(gè)設(shè)計(jì)模式中...
    Geeks_Liu閱讀 2,245評論 0 10
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,767評論 18 399