1. 程式人生 > >設計模式-單例模式(Singleton)各種寫法和分析比較

設計模式-單例模式(Singleton)各種寫法和分析比較

介紹

單例模式是設計模式中比較簡單容易理解的。它的出現主要是:

保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點

其實就在系統執行期間中保證只有這麼一個例項,並能夠全域性訪問。應用場景就是當需要一個物件時,這個物件需要整個系統執行期間只有一個,並且這個物件的新建開銷比較大,為了避免頻繁的新建物件浪費記憶體。就使用單例模式。

程式碼實現

單例模式有比較經典的兩種寫法。前段時間我去面試的有家公司面試題就有一題,寫兩種單例的程式碼實現並分析不同。所以這篇部落格也算是面試題分析吧。

懶漢式

懶漢的單例模式重點就在於懶,需要才去建立。

/**
 * Created by LiCola on  2016/05/07  20:39
 */
public class Singleton { //定義一個變數類儲存建立好的例項物件 預設為空 //因為需要在靜態方法中訪問 加上static 修飾符 private static Singleton instance =null; //私有化構造方法 防止外部建立物件 private Singleton() { } //提供一個類靜態方法返回單例物件 可以全域性訪問 public static Singleton getInstance(){ //懶就在這 當第一次有方法訪問才建立例項 //但是之後不會初始化物件
if (instance==null){ instance=new Singleton(); } return instance; } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

最簡單的懶漢單例模式就上面這寫程式碼,每行程式碼都有註釋說明,但是還要很多問題沒有說明清楚,下文再詳細分析。

餓漢式

餓漢單例模式程式碼和上面的差別不大,但是概念上需要引出Java虛擬機器的特性,有關static修飾符。

static修飾的變數在類裝載時進行初始化。 
多個例項的static變數會共享同一塊記憶體區域

分析這兩個特性,static變數只會在類裝載的時候初始化一次,並且多個例項共享記憶體區域,非常符合單例的需求。
重複一句話,餓漢的單例模式重點就在於餓,一餓就急就會急著用,沒有訪問也建立物件。

/**
 * Created by LiCola on  2016/05/07  21:05
 */
public class SingletonHunger {
    //定義變數儲存建立好的例項 
    //並且建立物件 只一次 在類載入的時候
    private static SingletonHunger instance = new SingletonHunger();

    //私有化構造方法 防止外部建立物件
    private SingletonHunger() {
    }

    //提供一個類靜態方法返回單例物件 可以全域性訪問
    public static SingletonHunger getInstance() {
        //類載入機制保證了例項的建立 就不需要做判斷 直接返回
        return instance;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

餓漢單例模式執行緒安全。

拓展說明

延遲載入

其實在懶漢單例模式中包含延遲載入的思想,所謂延遲載入就是一開始並不載入資源當有需要才去載入,在實際開發中是很常見的思想,儘可能的節約資源。具體體現就是:

    public static Singleton getInstance(){
        //這就體現了延遲載入,當有訪問使用才建立物件
        if (instance==null){
            instance=new Singleton();
        }
        return instance;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

快取

懶漢單例模式中還包含了快取的思想,這也是實際開發中很常見的功能和思想。 
簡單講,當有某個資源被頻繁使用,並且這些資源都是都在記憶體外,比如資料庫,硬碟檔案,網路資料等,每次的讀取都是耗時操作頻繁訪問會造成效能問題。 
快取就是解決這個問題的辦法之一,把資料快取到記憶體中,每次操作先從記憶體中尋找資源,如果有就直接使用,沒有就獲取資料並快取到記憶體中方便下次訪問。是一種典型的空間換時間的解決方案。 
快取在單例中的實現

    //這個就是被快取的例項
    private static Singleton instance =null;

    public static Singleton getInstance(){
        //判斷快取的變數是否為空  
        if (instance==null){
          //如果為空 就需要初始化操作 並賦值
            instance=new Singleton();
        }
        //如果有值 就直接返回
        return instance;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

Android開發中典型的快取應用場景就是圖片快取,hongyang大神和郭神都有部落格介紹使用Goggle提供的LruCache類實現快取。LruCache使用的就是LRU演算法

LRU:Least Recently Used 最近最少使用演算法,頁面置換演算法。

想不起來的同學面壁去吧,大學本科計算機作業系統就學過的內容。

快捷生成單例程式碼

在Android Studio中提供的快捷生成單例模式的程式碼就是餓漢模式。在程式碼檔案包右鍵彈出下圖,選中Singleton就能生成單例的程式碼。 
單例程式碼快捷生成操作

分析

既然有兩種單例寫法,它們之間存在差別和優缺點就需要分析。

時間和空間

這裡指的空間就是記憶體空間。

  • 懶漢式單例是典型的時間換空間,每次取值都要時間做判斷,判斷是否需要建立例項,當然如果沒有外部取值就不會建立物件,節約記憶體空間。
  • 餓漢式單例是典型的空間換時間,類裝載時就初始化例項,不管有沒有訪問取值,不需要做判斷節約時間,如果一直沒有外部訪問取值就浪費了記憶體空間。

執行緒安全

1.餓漢式單例是執行緒安全的,因為虛擬機器類載入機制保證了只會建立一次例項,並且裝載類的時候不會發生併發。 
2.上文的不加同步鎖的懶漢式單例是執行緒不安全的。 
下圖說明程式執行流程時候AB兩個執行緒訪問單例獲取方法,注意看時間軸上的方塊 
執行緒圖片描述

可以看到AB執行緒最後兩個時間塊在時間軸上部分重合,會創建出兩個例項物件來。單例控制失效。

執行緒安全的懶漢式單例

所以我們需要做的就是分隔時間塊,加同步鎖就可以實現,不是簡單的加鎖,而是雙重檢查加鎖即執行緒安全又能夠效能不受太大的影響。 
程式碼如下:

/**
 * Created by LiCola on  2016/05/07  22:54
 */
public class SynSingleton {
    //對儲存的物件 新增volatile關鍵字
    //volatile 修飾的變數值 不會被本地執行緒快取 
    //所有的操作都是直接操作共享記憶體 保證多個執行緒能夠正確的處理該變數 
    private volatile static SynSingleton instance = null;

    //私有化構造方法 防止外部建立物件
    private SynSingleton() {
    }

    //提供一個類靜態方法返回單例物件 可以全域性訪問
    public static SynSingleton getInstance() {
        //先檢查例項是否為空 不為空進入程式碼塊
        if (instance == null) {
            //同步塊 執行緒安全地建立例項
            synchronized (SynSingleton.class) {
                //再次檢查例項是否為空 為空才真正的建立例項
                if (instance == null) {
                    instance = new SynSingleton();
                }
            }
        }
        return instance;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

在開源的greenrobot的EventBus中,EventBus類就採用這樣的雙重檢查加鎖的單例模式。這裡提到EventBus就為為了下文我使用時遇到的坑埋伏筆。

提示:volatile 關鍵字可能會遮蔽掉虛擬機器中一些必要的程式碼優化,所以執行效率不是很高,一般建議,沒有特別需要慎重使用,

更好的單例實現方法

根據上文的分析,常見的兩種單例寫法都有不同問題和缺陷。最後還有一種寫法。即延遲載入又執行緒安全。這個方案稱為 Lazy initialization holder class模式,綜合使用Java的類級內部類和多執行緒預設同步鎖知識。

類級內部類

  • 類級內部類,指有static修飾的成員式內部類,如果沒有static修飾的就是物件級內部類
  • 類級內部類相當於外部類的static成分,它的物件與外部物件之間不存在依賴關係,因此可以直接建立。而物件級內部類的例項,繫結在外部物件的例項中。
  • 類級內部類中,可以定義靜態方法,靜態方法中只能夠引用外部類中的靜態成員方法或者成員變數
  • 類級內部類相當於其外部類的成員,只在第一次使用時才會被裝載。

多執行緒預設同步鎖

在多執行緒開發中除了使用synchronized同步鎖實現同步控制之外,虛擬機器有一些隱含的同步控制,這樣就不用我們控制同步,這些隱含的情況包括

  • 由靜態初始化器(靜態欄位或static{}程式碼塊的初始化器)初始化資料時
  • 訪問final欄位時
  • 在建立執行緒之前建立物件時
  • 執行緒可以看見它將要處理的物件時

實現程式碼

實現的程式碼思路就是,用靜態初始化器的方式,由虛擬機器保證執行緒安全。用類級內部類負責建立例項,只要不使用到這個類級內部類就不會建立例項。兩者結合就實現了延遲載入和執行緒安全。

/**
 - Created by LiCola on  2016/05/07  23:38
 */
public class HolderSingleton {
    /**
     * 類級內部類 也就是靜態的成員式內部類 該內部類的例項與外部類的例項沒有依賴
     * 而且只有被呼叫的時候才會被裝載,從而實現延遲載入
     */
    private static class SingletonHolder{
        //靜態初始化器 由虛擬機器保證執行緒安全
        private static HolderSingleton instance= new HolderSingleton();
    }

    private HolderSingleton() {
    }

    public static HolderSingleton getInstance(){
        return SingletonHolder.instance;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

上面的程式碼在執行中,當getInstance方法第一次被呼叫,它第一次讀取SingletonHolder.instance導致Singleton類得到初始化,而這個類在裝載時並同時被初始化,會初始化它的靜態域,從而建立Singleton例項,因為是靜態域,因此只會在虛擬機器裝載類的時候初始化一次,由虛擬機器保證它的執行緒安全。這樣寫的優勢就是,getInstance方法沒有被同步加鎖,並且只是執行一個域的訪問,延遲載入沒有增加任何的訪問開銷。

列舉單例

以為我們已經找到很好單例寫法和問題解決辦法。但是Java就是這麼有意思,總有新的想法提出和問題出現。 
前文的雙重檢查加鎖(double checked locking)單例在Java1.5之前也會某種情況下產生多個例項,並且volatile關鍵字也會導致的一些複雜的問題

在《Effective Java 》第二版第3條中,提到這一句話

單元素的列舉型別已經成為實現單例的最佳方法

這是在Java1.5發行版之後,列舉能夠實現單例,只需要編寫一個包含單個元素的列舉就可以實現執行緒安全程式碼簡單的單例。 
這裡需要說明列舉

  • Java的列舉型別實質上功能齊全的類,它有自己的屬性和方法
  • Java列舉型別的基本思想是通過公有的靜態final域為每個列舉常量匯出例項的類
  • 從某個角度上看,列舉是單例的泛型化,本質上是單元素的列舉

    程式碼

    概念這麼多,程式碼其實就幾行。

/**
 * Created by LiCola on  2016/05/08  15:16
 */
public enum  EnumSingleton {
    /**
     * 列舉元素 它就代表單例的一個例項
     */
    uniqueInstance;

    public void setUniqueInstance(){
        //對應普通單例的物件方法或者是功能操作
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

這種單例在功能上與公有域方法相近,但是程式碼簡潔,由虛擬機器提供序列化機制,絕對防止反射等方法導致的多次例項化。簡潔。高效、執行緒安全,真的可以說是最佳單例寫法。 
在使用的時候

         //這是普通單例的呼叫示例
        Singleton.getInstance().doOperate();

        //這是列舉單例的呼叫示例 沒有明顯的區別
        EnumSingleton.uniqueInstance.setUniqueInstance();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

總結

  1. 全文詳細說明兩個單例模式的寫法和優缺點,以及各自的不同。
  2. 在詳細分析之後,再延伸出三個單例的進化版雙重檢查加鎖和類級內部類單例以及列舉單例。都有詳細的註釋說明。
  3. 作為需要了解學習單例的寫法和分析已經足夠,但是實際開發中會遇到更復雜的情況,我下篇部落格再分析說明。

相關推薦

設計模式-模式(Singleton)各種寫法分析比較

介紹 單例模式是設計模式中比較簡單容易理解的。它的出現主要是: 保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點 其實就在系統執行期間中保證只有這麼一個例項,並能夠全域性訪問。應用場景就是當需要一個物件時,這個物件需要整個系統執行期間只有一個,並且這個物件的

設計模式-Singleton

don 設計模式 static sha 應用 ces zed void 內部類 2018-1-12 by Atlas UML UML中加“-”表示私有的(private); UML中加“+”表示公有的(public); UML中加“_”表示靜態的(static)

JAVA設計模式-模式(Singleton)線程安全與效率

保存 ring 使用方法 部分 rac cheng 原因 cts 要求 一,前言   單例模式詳細大家都已經非常熟悉了,在文章單例模式的八種寫法比較中,對單例模式的概念以及使用場景都做了很不錯的說明。請在閱讀本文之前,閱讀一下這篇文章,因為本文就是按照這篇文章中的八種單例模

設計模式01 建立型模式 - 模式(Singleton Pattern)

參考 [1] 設計模式之:建立型設計模式(6種) | 部落格園 [2] 單例模式的八種寫法比較 | 部落格園   單例模式(Singleton  Pattern) 確保一個類有且僅有一個例項,並且為客戶提供一個全域性訪問點。   特點 1) 保證被訪問資

用心理解設計模式——模式 (Singleton Pattern)

前置文章: 用心理解設計模式——設計模式的原則  設計模式相關程式碼已統一放至 我的 Github   一、定義   建立型模式之一。   Ensure a class has only one instance, an

設計模式-模式(Singleton)詳解

概述 定義 : 保證一個類僅有一個例項, 並提供一個全域性訪問點 又稱單件模式 型別 : 建立型 適用場景 想確保任何情況下都絕對只有一個例項 優點 在記憶體裡只有一個例項, 減少了記憶體開銷 可以避免對資源的多重

設計模式-模式 Singleton

1 好處 1、 避免例項物件的重複建立,節約記憶體空間 2、 能夠避免由於操作多個例項導致的邏輯錯誤 例子 1、 正常的new物件 Singleton ns = new Singleton(); System.out.println("-

JAVA設計模式-模式(Singleton)執行緒安全與效率

一,前言   單例模式詳細大家都已經非常熟悉了,在文章單例模式的八種寫法比較中,對單例模式的概念以及使用場景都做了很不錯的說明。請在閱讀本文之前,閱讀一下這篇文章,因為本文就是按照這篇文章中的八種單例模式進行探索的。   本文的目的是:結合文章中的八種單例模式的寫法,使用實際的示例,來演示執行緒安全和效率  

設計模式 - 模式(singleton pattern)

設計模式 - 單例模式(singleton pattern) 餓漢模式 public class Singleton{ private static final Singleton singleton = new Singleton (); private

設計模式-模式(Singleton)

單例模式 定義: 一個類有且僅有一個例項,並且自行例項化向整個系統提供 要點: 某個類只能有一個例項(類只提供私有的建構函式) 它必須自行建立這個例項(類定義中含有一個該類的靜態私有物件) 它必須自行向整個系統提供這個例項(該類提供了一個靜態的公有的函式用

設計模式-模式Singleton

單例物件(Singleton)是一種常用的設計模式。在Java應用中,單例物件能保證在一個JVM中,該物件只有一個例項存在。我們常把單例模式分兩種:飢漢模式和飽含模式。 1、飢漢模式 public class Singleton {          private st

設計模式——模式的幾種寫法

一、單例模式 單例模式是一種建立型的模式,指某個類採用單例模式後,在這個類被建立後,只產生一個例項以供外部訪問,且提供一個全域性的訪問點。 單例模式在開發中具有相當大的重要性,並且程式碼實現相對簡

設計模式-模式(Singleton)在Android中的應用場景實際使用遇到的問題

介紹 在上篇部落格中詳細說明了各種單例的寫法和問題。這篇主要介紹單例在Android開發中的各種應用場景以及和靜態類方法的對比考慮,舉實際例子說明。 單例的思考 寫了這麼多單例,都快忘記我們到底為什麼需要單例,複習單例的本質 單例的本質:控制例

設計模式--模式Singleton

........................................單例模式.............................................. 單例模式:即唯一

Java設計模式(三種寫法

最近在一些部落格上面看到的單例模式,無一例外都是餓漢和懶漢,這兩個確實是單例模式,但是各有自己的弊端 先上程式碼吧 //餓漢式 class ClassA{ private static final ClassA instance = new ClassA(); pub

設計模式模式Singleton

一、什麼是單例模式: 單例(Singleton)模式是一種常用的建立型設計模式。 簡單來說就是一個類只能構建一個物件的設計模式。 核心作用:保證一個類只有一個例項,並且提供一個訪問該例項的全域性訪問點。 二、單例模式的應用場景: 1、需要生成唯一序列的環境

[轉]設計模式--模式(一)懶漢式餓漢式

打印 是否 調用構造 餓漢 一段 tools 會有 輸出結果 java 單例模式是設計模式中比較簡單的一種。適合於一個類只有一個實例的情況,比如窗口管理器,打印緩沖池和文件系統, 它們都是原型的例子。典型的情況是,那些對象的類型被遍及一個軟件系統的不同對象訪問,因此需要一個

javascript設計模式-模式

空間 spa 靜態變量 通過 script 無法 單例 onf 訪問 單例模式,是創建型設計模式的一種,又被稱為單體模式,是只允許實例化一次的對象類。有時也用來規劃一個命名空間。 1 var Util = { 2 getName: function () {

設計模式--模式

final 簡單 封裝 產生 非線程安全 span 操作 ati zed 單例設計模式 Singleton是一種創建型模式,指某個類采用Singleton模式,則在這個類被創建後,只可能產生一個實例供外部訪問,並且提供一個全局的訪問點。 核心知識點如下: (1) 將采用單例

設計模式——模式

pre hostname turn cin user order 總結 -type path_info 單例模式 實例:web應用程序 #!/usr/bin/env python #coding:utf-8 from wsgiref.simple_server impor