設計模式之---單例模式

引言

大浪淘沙,洗盡鉛華無數,Java語言綿延數十載,前人留下了許多的輪子,Android使用java語言,自然需要用到這些輪子。今天,本少年就從頭擼一把設計模式界的hello world——單例模式。

介紹

  1. 定義:在Android中,單例模式其實使用頻率還是很高的,顧名思義,所謂單例就是指單個實例,如果一個類使用了單例模式,那么這個類的實例在整個APP中就只有一個實例。
  2. 應用場景:比如有一個util幫助類,它提供了一些功能,但是比較耗費資源,像線程池、IO訪存、數據庫操作等等。這種就不必new 很多實例,浪費資源。

幾種寫法

PS:XML布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="st.zlei.com.androidsingleton.MainActivity"
    android:orientation="vertical">


    <Button
        android:id="@+id/hungry_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="餓漢模式"
         />

    <Button
        android:id="@+id/lazy_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="基本懶漢模式" />

    <Button
        android:id="@+id/DCL_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="DCL單例模式" />

    <Button
        android:id="@+id/InnerClass_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="靜態內部類單例模式" />

    <Button
        android:id="@+id/map_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="容器單例模式" />
</LinearLayout>

這里寫圖片描述

1. 餓漢模式

上代碼:
單例類
Singleton_hungryMan 是單例的類。

public class Singleton_hungryMan {
    private static final String TAG = "TestSingle";
    private static Singleton_hungryMan singletonHungryMan = new Singleton_hungryMan();

    //構造方法為private,其他地方就無法new本類的對象
    private Singleton_hungryMan() {

    }
    //static以便類名.方法名調用
    public static Singleton_hungryMan getInstance(){
        return singletonHungryMan;
    }
    public void work(){
        Log.d(TAG, "onClick: "+"我是餓漢模式里面的work方法");
    }
}

調用Singleton_hungryMan

        //1.test餓漢模式
        hungry_button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //實例1:
                Singleton_hungryMan singletonHungryMan1 = Singleton_hungryMan.getInstance();
                Log.d(TAG, "onClick: "+singletonHungryMan1.hashCode());
                singletonHungryMan1.work();

                //實例2:
                Singleton_hungryMan singletonHungryMan2 = Singleton_hungryMan.getInstance();
                Log.d(TAG, "onClick: "+singletonHungryMan2.hashCode());
                singletonHungryMan2.work();
            }
        });

結果

這里寫圖片描述

描述:

Singleton_hungryMan的構造方法是private所以不能new,只能通過getInstance()獲取,而這個instance在聲明的時候就已經初始化了,因此,Singleton_hungryMan這個類的實例只有一個。從log的打印也可以看出來兩次得到的實例singletonHungryMan1 和singletonHungryMan12的哈希值是一樣的,說明這兩個對象指向的是同一個實例。

2. 基本的懶漢模式

上代碼:
單例類
Singleton_lazyMan 是單例的類。

public class Singleton_lazyMan {
    private static final String TAG = "TestSingle";

    //私有的構造方法
    private Singleton_lazyMan() {

    }

    private static Singleton_lazyMan singletonLazyMan = null;

    //synchronized保證多線程的同步問題
    public static synchronized Singleton_lazyMan getInstance(){
        if (singletonLazyMan == null){
            singletonLazyMan = new Singleton_lazyMan();
        }
        return singletonLazyMan;
    }

    public void work(){
        Log.d(TAG, "onClick: "+"我是基本的懶漢模式里面的work");
    }
}

*調用Singleton_lazyMan *

        //2.test基本的懶漢模式
        lazy_button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //實例1:
                Singleton_lazyMan singletonLazyMan1 = Singleton_lazyMan.getInstance();
                singletonLazyMan1.work();
                Log.d(TAG, "onClick: "+singletonLazyMan1.hashCode());

                //實例2:
                Singleton_lazyMan singletonLazyMan2 = Singleton_lazyMan.getInstance();
                singletonLazyMan2.work();
                Log.d(TAG, "onClick: "+singletonLazyMan2.hashCode());
            }
        });

結果

這里寫圖片描述

描述:

同上面一樣,兩個實例的哈希值是一樣的,說明單例的效果是達到了的。
synchronized的作用是保證多線程情況下單例對象的唯一性,試想當一個線程運行到這個地方的時候停下來了,另一個線程拿到了cpu去運行,是不是就會new 了兩個Singleton_lazyMan()


這里寫圖片描述

還有一個問題,這種懶漢模式在每次調用時都會用synchronized同步,這樣子會造成不必要的同步開銷。基于此,接下來將介紹一種改良版的懶漢模式。

3. 改良版的懶漢模式(DCL)

上代碼:
單例類
Sington_DCL 是單例的類。

public class Sington_DCL {
    private static final String TAG = "TestSingle";
    private Sington_DCL() {
    }

    private static Sington_DCL singtonDcl = null;

    public static Sington_DCL getInstance(){
        if (singtonDcl == null){
            synchronized (Sington_DCL.class){
                if (singtonDcl == null){
                    singtonDcl = new Sington_DCL();
                }
            }
        }
        return singtonDcl;
    }

    public void work(){
        Log.d(TAG, "onClick: "+"我是DCL模式里面的work");
    }
}

*調用Sington_DCL *

        //3.test DCL單例模式
        DCL_button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //實例1:
                Sington_DCL singtonDcl1 = Sington_DCL.getInstance();
                singtonDcl1.work();
                Log.d(TAG, "onClick: "+singtonDcl1.hashCode());

                //實例2:
                Sington_DCL singtonDcl2 = Sington_DCL.getInstance();
                singtonDcl2.work();
                Log.d(TAG, "onClick: "+singtonDcl2.hashCode());
            }
        });

結果

這里寫圖片描述

描述:

從結果的圖片看到,單例的效果是達到了的。
跟上面的懶漢模式相比,只有在第一次調用getInntance()才會同步,之后調用,便不會走synchronized里面的內容了,跟上面的本質區別還是同步鎖加的位置不同導致的。


這里需要注意有兩個null的判斷,第一個if判斷還是很好理解的,就是說,如果singtonDcl不是null就不必再執行new的操作了。第二個if判斷比較復雜,簡單的理解就是說:
singtonDcl = new Sington_DCL();
這句話在java中雖然就是一句,但是在底層CPU執行的時候是拆分成好幾步的:
1.給實例分配內存 2.調用構造函數,初始化成員字段 3.將對象singtonDcl 指向剛剛分配的內存空間
問題在于這三步走,第一步是最先執行,但是第二步和第三步是亂序的,也就是說不確定2和3誰先誰后
那么問題來了,舉個栗子,線程A執行了new Sington_DCL();`,底層cpu執行到1,3,即將執行2的時候線程B拿到了cpu,因為A已經把3執行了,這時候singtonDcl已經有內存空間了,換句話說singtonDcl已經不是null了,那么B就直接拿走了singtonDcl ,但事實上2還沒有沒有執行,B拿走的singtonDcl并不能用。所以還有一層if判斷。
但是想想,這種情況發生的概率非常低的呀。

4. 靜態內部類單例模式

上代碼:
單例類:

public class Singleton_InnerClass {
    private static final String TAG = "TestSingle";

    private Singleton_InnerClass() {
    }

    public static Singleton_InnerClass getInstance(){
        return SingletonHolder.mInstance;
    }

    private static class SingletonHolder {
        //第一次調用mInstance會初始化
        private static final Singleton_InnerClass mInstance = new Singleton_InnerClass();
    }

    public void work(){
        Log.d(TAG, "onClick: "+"我是靜態內部類單例模式里面的work");
    }
}

調用方式都一樣就不佳贅述了


5. 使用容器實現單例模式

上代碼:
單例類:

public class SIngleton_MapManager {

    private static final String TAG = "TestSingle";

    private static Map<String,Object> map = new HashMap<String,Object>();

    private SIngleton_MapManager() {
    }

    public static void registService(String key,Object obj){
        if (!map.containsKey(key)){
            map.put(key,obj);
        }
    }

    public static Object getService(String key){
        if (map.containsKey(key)){
            Object obj = map.get(key);
            return obj;
        }
        else {
            return null;
        }
    }
}

調用方式

        //5.test map容器單例模式
        map_button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                SIngleton_MapManager.registService("a","A");
                SIngleton_MapManager.registService("b","B");
                //實例1:
                Object service1 = SIngleton_MapManager.getService("a");
                Log.d(TAG, "onClick: "+service1+"-----"+service1.hashCode());

                //實例2:
                Object service2 = SIngleton_MapManager.getService("a");
                Log.d(TAG, "onClick: "+service2+"-----"+service2.hashCode());
            }
        });

描述:

這種方式很熟悉有沒有??沒錯,在Android中拿到系統的一些manager比如getSystemService(。。。)就是這種方式,這樣子系統核心服務都以單例存在,減少了資源消耗。平時自己寫單例模式好像不怎么用得到?

6. 總結

單例模式減少內存,減輕性能開銷,使用非常廣泛,本文介紹的幾種單例的寫法,推薦使用改良版的懶漢模式和靜態內部類的單例模式這兩種,當然單例還有其他寫法,但是比較小眾,這里就不再介紹了。

本文所使用的代碼可以在本人的github上找到,點這里傳送門走起

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,527評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,687評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,640評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,957評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,682評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,011評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,009評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,183評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,714評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,435評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,665評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,148評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,838評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,251評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,588評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,379評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,627評論 2 380

推薦閱讀更多精彩內容