1. 程式人生 > >你知道幾種單例模式?

你知道幾種單例模式?

大家好,先簡單自我介紹一下啊,我呢現在是在做Android這一塊,做這一塊大概快兩年了。工作地是在河南鄭州。在開始之前呢,首先感謝一下宇明兄弟啊,因為自己當初寫部落格的習慣是通過他的啟發才開始,現在又做這一塊語音文字的方式分享技術,我感覺挺有意義的,通過這種分享不僅能讓那些對分享內容感興趣的人學到知識,更重要的是分享著本人也能對知識有個更深的理解,當然如果有不理解或者疑問的地方,也能和大家討論解決困惑,我想這就是這個分享活動的真正意義。

目錄

  1. 什麼是設計模式?

  2. 設計模式的六大原則是什麼?

  3. 設計模式的分類?

  4. 什麼是單例模式?

  5. 單例模式常見的7種實現方式及優缺點

    1. 懶漢式

    2. 懶漢式(執行緒安全)

    3. 雙重檢測機制

    4. 餓漢式

    5. 靜態塊實現

    6. 靜態內部類實現

    7. 列舉

  6. 序列化對單例模式的影響

  7. 單例模式引起記憶體洩漏的解決方案

1

什麼是設計模式?

那現在正式開始今天的分享,設計模式這個詞大家應該都不陌生啊,在寫程式碼的時候很多人都會運用到設計模式。那什麼是設計模式呢。設計模式(Design pattern)是一套被反覆使用、多數人知曉的、是程式碼設計經驗的總結。使用設計模式是為了可重用程式碼、讓程式碼更容易被他人理解、保證程式碼可靠性。 毫無疑問,設計模式於己於他人於系統都是多贏的;設計模式的目標之一就是提高程式的可複用性。考慮的是怎樣才能將程式作為“元件”複用。

2

設計模式的六大原則是什麼?

  • 開閉原則(Open Close Principle):對擴充套件開放,對修改關閉

  • 里氏替換原則(Liskov Substitution Principle):子類可以擴充套件父類的功能,但不能改變父類原有的功能

  • 依賴倒轉原則(Dependence Inversion Principle):高層模組不應該依賴低層模組,二者都應該依賴其抽象;抽象不應該依賴細節;細節應該依賴抽象,核心思想是面向介面程式設計

  • 介面隔離原則(Interface Segregation Principle)客戶端不應該依賴它不需要的介面;一個類對另一個類的依賴應該建立在最小的介面上。

  • 迪米特法則(最少知道原則)(Demeter Principle)一個物件應該對其他物件保持最少的瞭解

  • 單一職責原則( Single responsibility principle )不要存在多於一個導致類變更的原因。通俗的說,即一個類只負責一項職責。

3

設計模式的分類

GOF設計模式分類:23種

建立型設計模式:

對類的例項化過程進行了抽象,能夠將軟體模組中物件的建立和物件的使用分離。

  • 工廠方法模式(Factory Method)

  • 抽象工廠模式(Abstract Factory)

  • 建立者模式(Builder)

  • 原型模式(Prototype)

  • 單例模式(Singleton)

結構型模式:

描述如何將類或者對 象結合在一起形成更大的結構,就像搭積木,可以通過 簡單積木的組合形成複雜的、功能更為強大的結構。

  • 外觀模式/門面模式(Facade門面模式)

  • 介面卡模式(Adapter)

  • 代理模式(Proxy)

  • 裝飾模式(Decorator)

  • 橋樑模式/橋接模式(Bridge)

  • 組合模式(Composite)

  • 享元模式(Flyweight)

行為型設計模式:

是對在不同的物件之間劃分責任和演算法的抽象化。

行為型模式不僅僅關注類和物件的結構,而且重點關注它們之間的相互作用

  • 模板方法模式(Template Method)

  • 觀察者模式(Observer)

  • 狀態模式(State)

  • 策略模式(Strategy)

  • 職責鏈模式(Chain of Responsibility)

  • 命令模式(Command)

  • 訪問者模式(Visitor)

  • 調停者模式(Mediator)

  • 備忘錄模式(Memento)

  • 迭代器模式(Iterator)

  • 直譯器模式(Interpreter)

4

什麼是單例模式?

程式在執行的時候,通常都會生成多個例項,例如表示字串的java.lang.String類的例項與字串是一對一的關係,所以當有一千個字串的時候,就會生成1000個例項,

許多時候整個系統只需要擁有一個的全域性物件,這樣有利於我們協調系統整體的行為。比如在某個伺服器程式中,該伺服器的配置資訊存放在一個檔案中,這些配置資料由一個單例物件統一讀取,然後服務程序中的其他物件再通過這個單例物件獲取這些配置資訊。這種方式簡化了在複雜環境下的配置管理。(維基百科)。

總結為確保任何情況下都絕對只有一個例項。或者想在程式上表現出“只存在一個例項”這就是單例模式。

5

單例模式常見的7種實現方式及優缺點

懶漢式執行緒不安全

在單例模式中,有一種稱為懶漢式的單例模式。顧名思義,懶漢式可以理解使用時才進行初始化,它包括私有的構造方法,私有的全域性靜態變數,公有的靜態方法,是一種懶載入機制。

/**

 * Created by Code4Android

 *1懶漢式執行緒不安全

 */

public class Singleton {

    private static Singleton instance;

    private Singleton() {

        System.out.println("初始化");

    }

    public static Singleton getInstance() {

        if (instance == null) {

            instance = new Singleton();

        }

        return instance;

    }

}

上面是最常見的懶漢式單例模式的寫法,但是如果在多執行緒的情況下,上述方法就會出現問題,它達不到只有一個單例物件的效果,例如當某個執行緒1調getInstance()方法並判斷instance == null,此時(就在判斷為空後new Singleton()之前)另一個執行緒2也呼叫getInstance()方法,由於此時執行緒1還沒有new出物件,則執行緒2執行getInstance()中instance 也為空,那麼此時就會出現多個例項的情況,而達不到只有一個例項的目的。

懶漢式(執行緒安全)

在上述實現中我們提到的懶漢式單例模式是一種非執行緒安全的,非執行緒安全即多執行緒訪問時會生成多個例項。那麼怎麼樣實現執行緒安全呢,也許你應該已經想到使用同步關鍵字synchronized。

/**

 * Created by Code4Android

 *2懶漢式執行緒安全:

 */

public class Singleton {

    private static Singleton instance;

    private Singleton() {

        System.out.println("初始化");

    }

    public static synchronized Singleton getInstance() {

        if (instance == null) {

            instance = new Singleton();

        }

        return instance;

    }

}

使用同步關鍵字後,也就實現了執行緒安全訪問,因為在任何時候它只能有一個執行緒呼叫 getInstance() 方法。那麼你可能會發出疑問,這樣加入同步,在高併發情況下,效率是很低的,因為真正需要同步的是我們第一次初始化的時候,是的,所以我們要進行進一步的優化。

雙重檢測機制

雙重檢測顧名思義就是兩次檢測,一次是檢測instance 例項是否為空,進行第一次過濾,在同步快中進行第二次檢測,因為當多個執行緒執行第一次檢測通過後會同時進入同步快,那麼此時就有必要進行第二次檢測來避免生成多個例項。

/**

 * Created by Code4Android

 *3DCL

 */

public class DCLSingleton {

    private static DCLSingleton instance;

    private DCLSingleton() {

        System.out.println("初始化");

    }

    public static synchronized  DCLSingleton getInstance() {

        if(instance==null){

            synchronized (DCLSingleton.class) {

                if (instance == null) {

                    instance = new DCLSingleton();

                }

            }

        }

        return instance;

    }

}

對於上面的程式碼時近乎完美的,既然說近乎完美,那肯定還是有瑕疵的,瑕疵出現的原因就是instance = new Singleton();這一句程式碼,你可能會問,這會有什麼問題,其實我也不知道,哈哈。

在計算機語言中,初始化包含了三個步驟

  1. 分配記憶體

  2. 執行構造方法初始化

  3. 將物件指向分配的記憶體空間

由於java編譯器為了儘可能減少記憶體操作速度遠慢於CPU執行速度所帶來的CPU空置的影響,虛擬機器會按照自己的一些規則(這規則後面再敘述)將程式編寫順序打亂——即寫在後面的程式碼在時間順序上可能會先執行,而寫在前面的程式碼會後執行——以儘可能充分地利用CPU就會出現指令重排序(happen-before),從而導致上面的三個步驟執行順序發生改變。正常情況下是123,但是如果指令重排後執行為1,3,2那麼久會導致instance 為空,進而導致程式出現問題。

既然已經知道了上述雙重檢測機制會出現問題,那麼我們該怎麼避免出現,該如何解決呢?

在java語言中有一個關鍵字volatile,我們都知道它提供了記憶體可見性這一特性,其實它還有一個作用就是防止指令重排序,那麼我們把變數singleton用volatile修飾下就可以了。

餓漢式

餓漢式與懶漢式區別是它在類載入的時候就進行初始化操作,而懶漢式是呼叫getInstance()方法時才進行初始化,有延遲載入的作用。

/**

 * Created by Code4Android

 *4餓漢式

 */

public class HungrySingleton {

    private static  HungrySingleton instance = new HungrySingleton();

    private HungrySingleton() {

        System.out.println("初始化");

    }

    public static HungrySingleton getInstance(){

        return instance;

    }

}

與懶漢式相比,它是執行緒安全的(無需用同步關鍵字修飾),由於沒有加鎖,執行效率也相對較高,但是也有一些缺點,在類載入時就初始化,會浪費記憶體。

靜態塊實現方式

/**

 * Created by Code4Android

 *5靜態塊

 */

public class HungrySingleton{

    private   HungrySingleton instance = null;

    //餓漢式變種5

    static {

        instance = new HungrySingleton();

    }

    private HungrySingleton() {

        System.out.println("初始化");

    }

    public static HungrySingleton getInstance(){

        return instance;

    }

}

靜態內部類實現方式

/**

 * Created by Code4Android on 2017/3/10.

 *6靜態內部類

 */

public class StaticInnerClassSingleton {

    private static class SingletonHolder {

        private static final StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();

    }

    private StaticInnerClassSingleton() {

        System.out.println("初始化");

    }

    public static final StaticInnerClassSingleton getInstance() {

        return SingletonHolder.INSTANCE;

    }

}

靜態內部類相對實現較為簡單,並且它是一種懶載入機制, 當Singleton 類被裝載了,instance 不一定被初始化。因為 SingletonHolder 類沒有被主動使用,只有顯示通過呼叫 getInstance 方法時,才會顯示裝載 SingletonHolder 類,從而例項化 instance。

列舉

/**

 * Created by Code4Android

 * 7預設列舉例項的建立是執行緒安全的

 */

public enum EnumSinglton {

    INSTANCE;

    private EnumSinglton() {

        System.out.println("構造方法");

    }

    public void  doSomething() {

        System.out.println("呼叫單例方法");

    }

}

列舉方式實現的單例模式是一種執行緒安全的單例模式。

6

序列化對單例模式的影響

通過上面我們對單例模式的學習,對單例模式有了進一步的學習,當我們有序列化的需求之後,那麼會產生怎樣的效果呢?先通過下面的程式碼來看下結果

public class Main {

    public static void main(String[] args) throws Exception {

        //測試序列化對單例模式的影響

        ByteArrayOutputStream bos = new ByteArrayOutputStream();

        new ObjectOutputStream(bos).writeObject(LazySingleton.getInstance());

        ByteArrayInputStream bin = new ByteArrayInputStream(bos.toByteArray());

        LazySingleton singleton = (LazySingleton) new ObjectInputStream(bin).readObject();

        ByteArrayOutputStream bos1 = new ByteArrayOutputStream();

        new ObjectOutputStream(bos1).writeObject(EnumSinglton.INSTANCE);

        ByteArrayInputStream bin1 = new ByteArrayInputStream(bos1.toByteArray());

        EnumSinglton singleton1 = (EnumSinglton) new ObjectInputStream(bin1).readObject();

        System.out.println(singleton == LazySingleton.getInstance());//false

        System.out.println(singleton1 == EnumSinglton.INSTANCE);//true

    }

}

通過我們的測試瞬時就懵逼了,除了通過列舉方式實現的單例模式,其它幾種實現都生成了多個例項。那麼該如何解決呢?難道反序列話只能生成多個例項。當然不是。我們可以看下面修改後的程式碼

public class HungrySingleton implements Serializable {

    private static  HungrySingleton instance = new HungrySingleton();

    private HungrySingleton() {

        System.out.println("初始化");

    }

    public static HungrySingleton getInstance(){

        return instance;

    }

    /**

     * 如果序列化,需要加入此方法,否則單例模式無效

     * @see java.io.ObjectStreamClass

     * @return

     */

    private Object readResolve() {

        return instance;

    }

}

單例模式引起記憶體洩漏的解決方案

public class LazySingleton {

    private static volatile LazySingleton instance;

    private Context context;

    private LazySingleton(Context context) {

        this.context=context;

        System.out.println("初始化");

    }

    public static   LazySingleton getInstance(Context context) {

        if(instance==null){

            synchronized (LazySingleton.class) {

                if (instance == null) {

                    instance = new LazySingleton(context);

                }

            }

        }

        return instance;

    }

}

上午在群裡聽到有人反映說使用單例模式造成了記憶體洩漏,那麼咱們就分析一下造成記憶體洩漏的原因,如上面程式碼,該單例模式中context是強引用,並且被static的變數instance持有,因為在java中靜態變數在類被裝載的時候分配記憶體,在被解除安裝的時候銷燬,也就是說它的生命週期就是應用的整個生命週期。那麼當我在一個Activity或者服務廣播中通過LazySingleton.getInstance(BaseActivity.this);獲取單例並使用,

由於LazySingleton的建立引用了Activity,所以導致系統GC的時候試圖去回收Activity時,發現它卻被單例LazySingleton所引用,所以GC回收失敗,進而導致記憶體洩漏。

既然我們明白了為什麼單例模式會導致記憶體洩漏,那麼解決也就簡單了,我們不需要傳入contex物件,可以使用Application的getApplicationContext即可。

當然我們可以使用弱引用WeakReference,它不同於強引用,它可以被系統回收,從而能解決避免記憶體洩漏。

相關推薦

知道模式

大家好,先簡單自我介紹一下啊,我呢現在是在做Android這一塊,做這一塊大概快兩年了。工作地是在河南鄭州。在開始之前呢,首先感謝一下宇明兄弟啊,因為自己當初寫部落格的習慣是通過他的啟發才開始,現在又做這一塊語音文字的方式分享技術,我感覺挺有意義的,通過這種分享不僅能讓那

常見的模式

任務 ron 解決方案 程序 反序 ola hat 例如 明顯   單例模式:是一種常用的軟件設計模式,在它的核心結構中值包含一個被稱為單例的特殊類。一個類只有一個實例,即一個類只有一個對象實例。   對於系統中的某些類來說,只有一個實例很重要,例如,一個系統中可以存在多個

android 模式的寫法

首先,先不論單例模式的寫法,有些方面是相同的,比如都需要將唯一的物件設定為static的,都需要將構造方法private化,程式碼如下: public class MyInstance { private static MyInstance instance;

java常用的模式(懶漢式、餓漢式、登記式)

簡單的講,單例模式就是確保某個類只有一個例項,而且自行例項化並向整個系統提供這個例項。 何時用到?執行緒池、快取、日誌物件、對話方塊、顯示卡驅動程式、印表機中都用到,spring中用的最多:Spring Context Factory用的是單例,bean預設都是

8模式寫法助搞定面試

1. 單例模式常見問題 為什麼要有單例模式 單例模式是一種設計模式,它限制了例項化一個物件的行為,始終至多隻有一個例項。當只需要一個物件來協調整個系統的操作時,這種模式就非常有用.它描述瞭如何解決重複出現的設計問題, 比如我們專案中的配置工具類,日誌工具類等等。 如何設計單例模式 ? 1.單例類如何控制其例項

快速理解Java中的五模式

嵌套類 ati class 由於 aop 適合 singleton 重復 code 解法一:只適合單線程環境(不好) package test; /** * @author xiaoping * */ public class Singleton { pri

模式與Object祖先類

三種單例模式 object 單例有三種模式,懶漢式,餓漢式,和優化後的懶漢式 餓漢式單例模式: 餓漢式就像饑餓的人一樣先把事情都提前準備好,因為它是先在靜態屬性裏先提前構建好對象,然後再用靜態方法將對象返回出去,所以會提前占用資源,但是速度比較快。例如:懶漢式單例模式: 懶漢式就像懶人一樣要等到事

模式----來自腳本之家

example cin args pytho single main 屬性 code 添加 本文為大家分享了Python創建單例模式的5種常用方法,供大家參考,具體內容如下 所謂單例,是指一個類的實例從始至終只能被創建一次。 方法1: 如果想使得某個類從始至終最多只有一個

網頁載入慢,知道原因?

記得以前有個培訓班的老師過來宣傳,他當時問了我們一個問題,“開啟一個網頁慢,你能說出10個原因麼?”,我腦海裡立刻就出現了網速慢、電腦卡等原因,但是發現自己能說出的不超過五個,自己還是學web的,GG。今天突然想到了這個問題,就總結下 頻寬不足,首先想到的就是自己網速的

Kotlin實現常用的五模式

廢話不多說,直接上程式碼,記錄一下,方便以後使用 class ImageClassifyUtil private constructor(){ /** * 餓漢 */ companion object { val instance

C++的三模式-----深度解析

三種單例模式轉自部落格:http://blog.csdn.net/q_l_s/article/details/52369065 小編想要對三種的單例模式做下解析 簡介         因為在設計或開發中,肯定會有這麼一種情況,一個類只能有一個物件

Java中5建立物件的方法,知道

作為一個Java開發者,一種面向物件的語言,我們每天都建立很多物件。但後續我們開發中,採用了spring的依賴管理系統,我們就很少自己去建立物件了,全部交給容器去託管,那麼本篇文章回源塑本,講述一下java中能夠建立一個物件的5中方法。 本文最大的特色是,我不僅給出案例,

C#中三模式

一、經典模式:     public class DoSomething     {         private static DoSomething doSomething;         private DoSomething() { }         pu

Nginx實現404頁面的方法,知道

一個網站專案,肯定是避免不了404頁面的,通常使用Nginx作為Web伺服器時,有以下集中配置方式,一起來看看。 第一種:Nginx自己的錯誤頁面 Nginx訪問一個靜態的html 頁面,當這個頁面沒有的時候,Nginx丟擲404,那麼如何返回給客戶端404呢? 看下面的配置,這種情況下不需要修改

阿里P7講解ava實現——真的會寫模式

單例模式可能是程式碼最少的模式了,但是少不一定意味著簡單,想要用好、用對單例模式,還真得費一番腦筋。本文對Java中常見的單例模式寫法做了一個總結,如有錯漏之處,懇請讀者指正。 餓漢法 顧名思義,餓漢法就是在第一次引用該類的時候就建立物件例項,而不管實際是否需要建立。程式碼如下:

20程式語言的hello world,知道

20種程式語言的hello world 你知道幾種................. 進群進群:943752371可以獲取Python各類入門學習資料! 這是我的微信公眾號【Python程式設計之家】各位大佬用空可以關注下,每天更新Python學習方法,感謝! 1111111111

模式比較(懶漢式、惡漢式)

Java 是一種面向物件的程式語言,通常一個類都有很多物件。實際應用中,我們需要某個特定的類只有一個物件,這就是單例模式。 注意:單例物件都是靜態的物件,為了保證物件是單例物件,必須私有化構造方法,並提供一個公共的靜態方法供外界呼叫來取得單例物件。 懶漢式單

模式的C++實現

簡介         因為在設計或開發中,肯定會有這麼一種情況,一個類只能有一個物件被建立,如果有多個物件的話,可能會導致狀態的混亂和不一致。這種情況下,單例模式是最恰當的解決辦法。它有很多種實現方式,各自的特性不相同,使用的情形也不相同。今天要實現的是常用的三種,分別

Java五模式與執行緒安全

單例模式是一種常用的軟體設計模式,常被用於一個類在系統中最多隻允許存在一個例項的場合,如視窗管理器、列印緩衝池、檔案系統等。在單例模式的核心結構中,只包含一個被稱為單例類的特殊類。通過單例模式可以保證系統中一個類只有一個例項而且該例項易於外界訪問,從而方便對例項個數的控制並節約系統資源。如果希望在系統中某個類

模式

1、餓漢式 public class SingletonHungry { private SingletonHungry () { } private static Sin