Carson帶你學設計模式:單例模式(Singleton)

前言

今天Carson來全面總結最常用的設計模式 - 單例模式。

其他設計模式介紹
1分鐘全面了解“設計模式”
單例模式(Singleton) - 最易懂的設計模式解析
簡單工廠模式(SimpleFactoryPattern)- 最易懂的設計模式解析
工廠方法模式(Factory Method)- 最易懂的設計模式解析
抽象工廠模式(Abstract Factory)- 最易懂的設計模式解析
策略模式(Strategy Pattern)- 最易懂的設計模式解析
適配器模式(Adapter Pattern)- 最易懂的設計模式解析
代理模式(Proxy Pattern)- 最易懂的設計模式解析
模板方法模式(Template Method) - 最易懂的設計模式解析
建造者模式(Builder Pattern)- 最易懂的設計模式解析
外觀模式(Facade Pattern) - 最易懂的設計模式解析


目錄

示意圖

1. 實例引入

  • 背景:小成有一個塑料生產(chǎn)廠,但里面只有一個倉庫。
  • 目的:想用代碼來實現(xiàn)倉庫的管理
  • 現(xiàn)有做法: 建立倉庫類和工人類

    其中,倉庫類里的quantity=商品數(shù)量;工人類里有搬運方法MoveIn(int i)和MoveOut(int i)。

  • 出現(xiàn)的問題:通過測試發(fā)現(xiàn),每次工人搬運操作都會新建一個倉庫,就是貨物都不是放在同一倉庫,這是怎么回事呢?(看下面代碼)
package scut.designmodel.SingletonPattern;


//倉庫類
class StoreHouse {
    private int quantity = 100;

    public void setQuantity(int quantity) {
        this.quantity = quantity;
    }

    public int getQuantity() {
        return quantity;
    }
}

//搬貨工人類
class Carrier{
    public StoreHouse mStoreHouse;
    public Carrier(StoreHouse storeHouse){
        mStoreHouse = storeHouse;
    }
    //搬貨進倉庫
    public void MoveIn(int i){
        mStoreHouse.setQuantity(mStoreHouse.getQuantity()+i);
    }
    //搬貨出倉庫
    public void MoveOut(int i){
        mStoreHouse.setQuantity(mStoreHouse.getQuantity()-i);
    }
}

//工人搬運測試
public class SinglePattern {
    public static void main(String[] args){
        StoreHouse mStoreHouse1 = new StoreHouse();
        StoreHouse mStoreHouse2 = new StoreHouse();
        Carrier Carrier1 = new Carrier(mStoreHouse1);
        Carrier Carrier2 = new Carrier(mStoreHouse2);

        System.out.println("兩個是不是同一個?");

        if(mStoreHouse1.equals(mStoreHouse2)){//這里用equals而不是用 == 符號,因為 == 符號只是比較兩個對象的地址
            System.out.println("是同一個");
        }else {
            System.out.println("不是同一個");
        }
        //搬運工搬完貨物之后出來匯報倉庫商品數(shù)量
        Carrier1.MoveIn(30);
        System.out.println("倉庫商品余量:"+Carrier1.mStoreHouse.getQuantity());
        Carrier2.MoveOut(50);
        System.out.println("倉庫商品余量:"+Carrier2.mStoreHouse.getQuantity());
    }
}


結果:

兩個是不是同一個?
不是同一個
倉庫商品余量:130
倉庫商品余量:50

2. 單例模式介紹

2.1 模式說明

實現(xiàn)1個類只有1個實例化對象 & 提供一個全局訪問點

2.2 作用(解決的問題)

保證1個類只有1個對象,降低對象之間的耦合度

從上面可看出:工人類操作的明顯不是同一個倉庫實例,而全部工人希望操作的是同一個倉庫實例,即只有1個實例

2.3 工作原理

在Java中,我們通過使用對象(類實例化后)來操作這些類,類實例化是通過它的構造方法進行的,要是想實現(xiàn)一個類只有一個實例化對象,就要對類的構造方法下功夫:

單例模式的原理

單例模式的一般實現(xiàn):(含使用步驟)

 public class Singleton {
//1. 創(chuàng)建私有變量 ourInstance(用以記錄 Singleton 的唯一實例)
//2. 內(nèi)部進行實例化
    private static Singleton ourInstance  = new  Singleton();

//3. 把類的構造方法私有化,不讓外部調(diào)用構造方法實例化
    private Singleton() {
    }
//4. 定義公有方法提供該類的全局唯一訪問點
//5. 外部通過調(diào)用getInstance()方法來返回唯一的實例
    public static  Singleton newInstance() {
        return ourInstance;
    }
}

好了,單例模式的介紹和原理應該了解了吧?那么我們現(xiàn)在來解決上面小成出現(xiàn)的“倉庫不是一個”的問題吧!


3. 實例講解

小成使用單例模式改善上面例子的代碼:

package scut.designmodel.SingletonPattern;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

//單例倉庫類
class StoreHouse {

    //倉庫商品數(shù)量
    private int quantity = 100;
    //自己在內(nèi)部實例化
    private static StoreHouse ourInstance  = new StoreHouse();;
    //讓外部通過調(diào)用getInstance()方法來返回唯一的實例。
    public static StoreHouse getInstance() {
        return ourInstance;
    }

    //封閉構造函數(shù)
    private StoreHouse() {
    }

    public void setQuantity(int quantity) {
        this.quantity = quantity;
    }

    public int getQuantity() {
        return quantity;
    }
}


//搬貨工人類
class Carrier{
    public StoreHouse mStoreHouse;
    public Carrier(StoreHouse storeHouse){
        mStoreHouse = storeHouse;
    }
    //搬貨進倉庫
    public void MoveIn(int i){
        mStoreHouse.setQuantity(mStoreHouse.getQuantity()+i);
    }
    //搬貨出倉庫
    public void MoveOut(int i){
        mStoreHouse.setQuantity(mStoreHouse.getQuantity()-i);
    }
}

//工人搬運測試
public class SinglePattern {
    public static void main(String[] args){
        StoreHouse mStoreHouse1 = StoreHouse.getInstance();
        StoreHouse mStoreHouse2 = StoreHouse.getInstance();
        Carrier Carrier1 = new Carrier(mStoreHouse1);
        Carrier Carrier2 = new Carrier(mStoreHouse2);

        System.out.println("兩個是不是同一個?");

        if(mStoreHouse1.equals(mStoreHouse2)){
            System.out.println("是同一個");
        }else {
            System.out.println("不是同一個");
        }
        //搬運工搬完貨物之后出來匯報倉庫商品數(shù)量
        Carrier1.MoveIn(30);
        System.out.println("倉庫商品余量:"+Carrier1.mStoreHouse.getQuantity());
        Carrier2.MoveOut(50);
        System.out.println("倉庫商品余量:"+Carrier2.mStoreHouse.getQuantity());
    }
}


結果:

兩個是不是同一個?
是同一個
倉庫商品余量:130
倉庫商品余量:80

從結果分析,使用了單例模式后,倉庫類就只有一個倉庫實例了,再也不用擔心搬運工人進錯倉庫了!!!


4. 特點

4.1 優(yōu)點

  • 提供了對唯一實例的受控訪問;
  • 由于在系統(tǒng)內(nèi)存中只存在一個對象,因此可以節(jié)約系統(tǒng)資源,對于一些需要頻繁創(chuàng)建和銷毀的對象單例模式無疑可以提高系統(tǒng)的性能;
  • 可以根據(jù)實際情況需要,在單例模式的基礎上擴展做出雙例模式,多例模式;

4.2 缺點

  1. 單例類的職責過重,里面的代碼可能會過于復雜,在一定程度上違背了“單一職責原則”。
  2. 如果實例化的對象長時間不被利用,會被系統(tǒng)認為是垃圾而被回收,這將導致對象狀態(tài)的丟失。

5. 單例模式的實現(xiàn)方式

  • 單例模式的實現(xiàn)方式有多種,根據(jù)需求場景,可分為2大類、6種實現(xiàn)方式。具體如下:
示意圖
  • 下面,我將詳細介紹每種單例模式的實現(xiàn)方式

a. 初始化單例類時 即 創(chuàng)建單例

1. 餓漢式

這是 最簡單的單例實現(xiàn)方式

  • 原理
    依賴 JVM類加載機制,保證單例只會被創(chuàng)建1次,即 線程安全
  1. JVM在類的初始化階段(即 在Class被加載后、被線程使用前),會執(zhí)行類的初始化
  2. 在執(zhí)行類的初始化期間,JVM會去獲取一個鎖。這個鎖可以同步多個線程對同一個類的初始化
  • 具體實現(xiàn)
class Singleton {

    // 1. 加載該類時,單例就會自動被創(chuàng)建
    private static  Singleton ourInstance  = new  Singleton();
    
    // 2. 構造函數(shù) 設置為 私有權限
    // 原因:禁止他人創(chuàng)建實例 
    private Singleton() {
    }
    
    // 3. 通過調(diào)用靜態(tài)方法獲得創(chuàng)建的單例
    public static  Singleton newInstance() {
        return ourInstance;
    }
}
  • 應用場景
    除了初始化單例類時 即 創(chuàng)建單例外,繼續(xù)延伸出來的是:單例對象 要求初始化速度快 & 占用內(nèi)存小
2. 枚舉類型
  • 原理
    根據(jù)枚舉類型的下述特點,滿足單例模式所需的 創(chuàng)建單例、線程安全、實現(xiàn)簡潔的需求
示意圖
  • 實現(xiàn)方式
public enum Singleton{

    //定義1個枚舉的元素,即為單例類的1個實例
    INSTANCE;

    // 隱藏了1個空的、私有的 構造方法
    // private Singleton () {}

}

// 獲取單例的方式:
Singleton singleton = Singleton.INSTANCE;
  • 注:這是 最簡潔、易用 的單例實現(xiàn)方式,借用《Effective Java》的話:

單元素的枚舉類型已經(jīng)成為實現(xiàn) Singleton的最佳方法


b. 按需、延遲創(chuàng)建單例

1. 懶漢式(基礎實現(xiàn))
  • 原理

與 餓漢式 最大的區(qū)別是:單例創(chuàng)建的時機

  • 餓漢式:單例創(chuàng)建時機不可控,即類加載時 自動創(chuàng)建 單例
  • 懶漢式:單例創(chuàng)建時機可控,即有需要時,才 手動創(chuàng)建 單例
  • 具體實現(xiàn)
class Singleton {
    // 1. 類加載時,先不自動創(chuàng)建單例
   //  即,將單例的引用先賦值為 Null
    private static  Singleton ourInstance  = null;

    // 2. 構造函數(shù) 設置為 私有權限
    // 原因:禁止他人創(chuàng)建實例 
    private Singleton() {
    }
    
    // 3. 需要時才手動調(diào)用 newInstance() 創(chuàng)建 單例   
    public static  Singleton newInstance() {
    // 先判斷單例是否為空,以避免重復創(chuàng)建
    if( ourInstance == null){
        ourInstance = new Singleton();
        }
        return ourInstance;
    }
}
  • 缺點
    基礎實現(xiàn)的懶漢式是線程不安全的,具體原因如下
示意圖
  • 下面,將對懶漢式 進行優(yōu)化,使得適合在多線程環(huán)境下運行
2. 同步鎖(懶漢式的改進)
  • 原理
    使用同步鎖 synchronized鎖住 創(chuàng)建單例的方法 ,防止多個線程同時調(diào)用,從而避免造成單例被多次創(chuàng)建
  1. 即,getInstance()方法塊只能運行在1個線程中
  2. 若該段代碼已在1個線程中運行,另外1個線程試圖運行該塊代碼,則 會被阻塞而一直等待
  3. 而在這個線程安全的方法里我們實現(xiàn)了單例的創(chuàng)建,保證了多線程模式下 單例對象的唯一性
  • 具體實現(xiàn)
// 寫法1
class Singleton {
    // 1. 類加載時,先不自動創(chuàng)建單例
    //  即,將單例的引用先賦值為 Null
    private static  Singleton ourInstance  = null;
    
    // 2. 構造函數(shù) 設置為 私有權限
    // 原因:禁止他人創(chuàng)建實例 
    private Singleton() {
    }
    
// 3. 加入同步鎖
public static synchronized Singleton getInstance(){
        // 先判斷單例是否為空,以避免重復創(chuàng)建
        if ( ourInstance == null )
            ourInstance = new Singleton();
        return ourInstance;
    }
}


// 寫法2
// 該寫法的作用與上述寫法作用相同,只是寫法有所區(qū)別
class Singleton{ 

    private static Singleton instance = null;

    private Singleton(){
}

    public static Singleton getInstance(){
        // 加入同步鎖
        synchronized(Singleton.class) {
            if (instance == null)
                instance = new Singleton();
        }
        return instance;
    }
}


  • 缺點
    每次訪問都要進行線程同步(即 調(diào)用synchronized鎖),造成過多的同步開銷(加鎖 = 耗時、耗能)

實際上只需在第1次調(diào)用該方法時才需要同步,一旦單例創(chuàng)建成功后,就沒必要進行同步

3. 雙重校驗鎖(懶漢式的改進)
  • 原理
    在同步鎖的基礎上,添加1層 if判斷:若單例已創(chuàng)建,則不需再執(zhí)行加鎖操作就可獲取實例,從而提高性能

  • 具體實現(xiàn)

class Singleton {
    private static  Singleton ourInstance  = null;

    private Singleton() {
    }
    
    public static  Singleton newInstance() {
     // 加入雙重校驗鎖
    // 校驗鎖1:第1個if
    if( ourInstance == null){  // ①
     synchronized (Singleton.class){ // ②
      // 校驗鎖2:第2個 if
      if( ourInstance == null){
          ourInstance = new Singleton();
          }
      }
  }
        return ourInstance;
   }
}

// 說明
// 校驗鎖1:第1個if
// 作用:若單例已創(chuàng)建,則直接返回已創(chuàng)建的單例,無需再執(zhí)行加鎖操作
// 即直接跳到執(zhí)行 return ourInstance

// 校驗鎖2:第2個 if 
// 作用:防止多次創(chuàng)建單例問題
// 原理
  // 1. 線程A調(diào)用newInstance(),當運行到②位置時,此時線程B也調(diào)用了newInstance()
  // 2. 因線程A并沒有執(zhí)行instance = new Singleton();,此時instance仍為空,因此線程B能突破第1層 if 判斷,運行到①位置等待synchronized中的A線程執(zhí)行完畢
  // 3. 當線程A釋放同步鎖時,單例已創(chuàng)建,即instance已非空
  // 4. 此時線程B 從①開始執(zhí)行到位置②。此時第2層 if 判斷 = 為空(單例已創(chuàng)建),因此也不會創(chuàng)建多余的實例
  • 缺點
    實現(xiàn)復雜 = 多種判斷,易出錯
4. 靜態(tài)內(nèi)部類
  • 原理
    根據(jù) 靜態(tài)內(nèi)部類 的特性,同時解決了按需加載、線程安全的問題,同時實現(xiàn)簡潔
  1. 在靜態(tài)內(nèi)部類里創(chuàng)建單例,在裝載該內(nèi)部類時才會去創(chuàng)建單例
  2. 線程安全:類是由 JVM加載,而JVM只會加載1遍,保證只有1個單例
  • 具體實現(xiàn)
class Singleton {
    
    // 1. 創(chuàng)建靜態(tài)內(nèi)部類
    private static class Singleton2 {
       // 在靜態(tài)內(nèi)部類里創(chuàng)建單例
      private static  Singleton ourInstance  = new Singleton();
    }

    // 私有構造函數(shù)
    private Singleton() {
    }
    
    // 延遲加載、按需創(chuàng)建
    public static  Singleton newInstance() {
        return Singleton2.ourInstance;
    }

}

// 調(diào)用過程說明:
      // 1. 外部調(diào)用類的newInstance() 
      // 2. 自動調(diào)用Singleton2.ourInstance
       // 2.1 此時單例類Singleton2得到初始化
       // 2.2 而該類在裝載 & 被初始化時,會初始化它的靜態(tài)域,從而創(chuàng)建單例;
       // 2.3 由于是靜態(tài)域,因此只會JVM只會加載1遍,Java虛擬機保證了線程安全性
      // 3. 最終只創(chuàng)建1個單例

6. 總結

  • 本文主要對 單例模式 進行了全面介紹,包括原理 & 實現(xiàn)方式
  • 對于實現(xiàn)方式,此處作出總結
示意圖

接下來我會對每種設計模式進行詳細的分析,歡迎關注Carson_Ho的簡書,不定期分享關于安卓開發(fā)的干貨,追求短、平、快,但卻不缺深度


請點贊!因為你的鼓勵是我寫作的最大動力!

相關文章閱讀
單例模式(Singleton) - 最易懂的設計模式解析
簡單工廠模式(SimpleFactoryPattern)- 最易懂的設計模式解析
工廠方法模式(Factory Method)- 最易懂的設計模式解析
抽象工廠模式(Abstract Factory)- 最易懂的設計模式解析
策略模式(Strategy Pattern)- 最易懂的設計模式解析
適配器模式(Adapter Pattern)- 最易懂的設計模式解析
代理模式(Proxy Pattern)- 最易懂的設計模式解析
模板方法模式(Template Method) - 最易懂的設計模式解析
建造者模式(Builder Pattern)- 最易懂的設計模式解析
外觀模式(Facade Pattern) - 最易懂的設計模式解析

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

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