設計模式單例模式

NO IMAGE

序言

無論是面試以及日常都是我們最常用的模式之一,就是單例模式,也應該是所有設計模式中最簡單最好理解的模式,這種模式屬於創造性模式,並且提供了創造對象最佳的一種方式,這種模式設計單一模式。這個類提供了一種訪問其唯一的對象的方式,可以直接訪問,並且不需要實例化該類的對象。
該文章會解決如下問題:

  1. 為什麼要用單例模式?
  2. 單例模式如何用?有什麼注意的?
  3. 單例模式需要注意的問題

為什麼要用單例模式?

首先解決什麼是單例模式:保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。
那有什麼優點呢:如果這個類屬於全局使用,就不需要頻繁的創建或者銷燬,那麼單例模式的優勢就出來了,突出全局可以使用,並且全局可以調取,全局也只有一個實例,並不需要維護多個實例問題。

  • 在內存裡只有一個實例,減少了內存的開銷,尤其是頻繁的創建和銷燬實例(比如一個全局的工具類)。
  • 避免對資源的多重佔用(比如寫文件操作)。

缺點也是同樣明顯:沒有接口,不能繼承,與單一職責原則衝突,一個類應該只關心內部邏輯,而不關心外面怎麼樣來實例化。

單例模式如何使用?有什麼注意的?

這邊就是如何實現問題,單例模式有7中實現方法,以下會把所有的實現方法寫出來,並對比優缺點,有一句話沒有最好的只有最合適的,根據不同的場景用不同的方法來寫,才是 Top Coder

  1. 懶漢式
public class Singleton {  
    //在 Singleton 生成一個 instance
    private static Singleton instance;  
    //無參構造
    private Singleton (){}  
  
    //提供獲取 Singleton 實例的方法,如果沒有則創建一個
    public static Singleton getInstance() {  
    if (instance == null) {  
        instance = new Singleton();  
    }  
    return instance;  
    }  
}

分析一下代碼,創建的原理很簡單,創建一個靜態實例,當其他類會調用 getInstance() 方法的時候判斷當前是否有這個實例,沒有則創建,然後返回實例,這種 Lazy 初始化,問題很明顯,若是多線程調用則會出現線程不安全。
2. 飽漢式

public class Singleton {  
   //在 Singleton 生成一個 instance
    private static Singleton instance;  
    //無參構造
    private Singleton (){}  

   //提供獲取 Singleton 實例的方法,如果沒有則創建一個,但是與餓漢不同,加入了線程鎖
    public static synchronized Singleton getInstance() {  
    if (instance == null) {  
        instance = new Singleton();  
    }  
    return instance;  
    }  
}

這種方式具備很好的 lazy loading,能夠在多線程中很好的工作,但是,效率很低,99% 情況下不需要同步。第一次調用才初始化,避免內存浪費。必須加鎖 synchronized 才能保證單例,但加鎖會影響效率。getInstance() 的性能對應用程序不是很關鍵(該方法使用不太頻繁)
3. 餓漢式

public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
    return instance;  
    }  
}

分析一下源碼,這是比較常用的一種創建方式,但是比較容易出現浪費內存,因為不存在 lazy loading 的方式,它基於 classloader 機制避免了多線程的同步問題,不過,instance 在類裝載時就實例化,雖然導致類裝載的原因有很多種,在單例模式中大多數都是調用 getInstance 方法, 但是也不能確定有其他的方式(或者其他的靜態方法)導致類裝載。

  1. 雙檢鎖/雙重校驗鎖(DCL,即 double-checked locking)
public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {  
        synchronized (Singleton.class) {  
        if (singleton == null) {  
            singleton = new Singleton();  
        }  
        }  
    }  
    return singleton;  
    }  
}

可以看到,源碼中加了雙鎖,以避免多線程效率低的問題,用於 getInstance() 的性能對應用程序很關鍵。有 lazyloading 模式,但是實現起來會比較麻煩
5. 登記式/靜態內部類

public class Singleton {  
    private static class SingletonHolder {  
        private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
        return SingletonHolder.INSTANCE;  
    }  
}

這種方式能達到雙檢鎖方式一樣的功效,但實現更簡單。對靜態域使用延遲初始化,應使用這種方式而不是雙檢鎖方式。這種方式只適用於靜態域的情況,雙檢鎖方式可在實例域需要延遲初始化時使用。這種方式同樣利用了 classloader 機制來保證初始化 instance 時只有一個線程,它跟第 3 種方式不同的是:第 3 種方式只要 Singleton 類被裝載了,那麼 instance 就會被實例化(沒有達到 lazy loading 效果),而這種方式是 Singleton 類被裝載了,instance 不一定被初始化。因為 SingletonHolder 類沒有被主動使用,只有通過顯式調用 getInstance 方法時,才會顯式裝載 SingletonHolder 類,從而實例化 instance。想象一下,如果實例化 instance 很消耗資源,所以想讓它延遲加載,另外一方面,又不希望在 Singleton 類加載時就實例化,因為不能確保 Singleton 類還可能在其他的地方被主動使用從而被加載,那麼這個時候實例化 instance 顯然是不合適的。這個時候,這種方式相比第 3 種方式就顯得很合理。

  1. 枚舉
public enum Singleton {  
    INSTANCE;  
    public void whateverMethod() {  
    }  
}

這種實現方式還沒有被廣泛採用,但這是實現單例模式的最佳方法。它更簡潔,自動支持序列化機制,絕對防止多次實例化。這種方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不僅能避免多線程同步問題,而且還自動支持序列化機制,防止反序列化重新創建新的對象,絕對防止多次實例化。不過,由於 JDK1.5 之後才加入 enum 特性,用這種方式寫不免讓人感覺生疏,在實際工作中,也很少用。不能通過 reflection attack 來調用私有構造方法。

  1. 使用容器實現單例模式
public class SingletonManager { 
&emsp;&emsp;private static Map<String, Object> objMap = new HashMap<String,Object>();
&emsp;&emsp;private Singleton() { 
&emsp;&emsp;}
&emsp;&emsp;public static void registerService(String key, Objectinstance) {
&emsp;&emsp;&emsp;&emsp;if (!objMap.containsKey(key) ) {
&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;objMap.put(key, instance) ;
&emsp;&emsp;&emsp;&emsp;}
&emsp;&emsp;}
&emsp;&emsp;public static ObjectgetService(String key) {
&emsp;&emsp;&emsp;&emsp;return objMap.get(key) ;
&emsp;&emsp;}
}

用SingletonManager 將多種的單例類統一管理,在使用時根據key獲取對象對應類型的對象。這種方式使得我們可以管理多種類型的單例,並且在使用時可以通過統一的接口進行獲取操作,降低了用戶的使用成本,也對用戶隱藏了具體實現,降低了耦合度。

單例模式需要注意的問題

有這麼多種模式,我在工作中用的最多的是第六種,雖然用的人不是很多,但是確實會比較好用,也解決很多問題,可以嘗試使用一下,當然DCL 的模式也很不錯,一般面試會考懶漢式和飽漢式,建議根據掌握,如果能掌握枚舉也是很加分的!

相關文章

Android前端Java後端集成支付寶支付

postman使用Androidjava後端接口調試工具

Android抓包Charleshttp接口調試

小米殺不死的推送Android、java後端同時接入小米推送